This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
I have been working on a few different conditions and I am looking for suggestions/clarity on if I am using the tool incorrectly and if I am, how do I go about performing my tasks. I have a database model that has a collection of OrderItems. It has a back reference to a Product. I have an aggregate called Populated but I choose not to include it on the back reference because I may not want the Product to load until / if the product is accessed. When I load up an order item or a product using the aggregate, many child items are populated and it works great. My problem is that when I “touch” the Product property, it is not smart enough to load that entity with the aggregate used when I load the Order Item. My question is: is there a way to provide an aggregate so that the product can be loaded up using that aggregate if someone touches the property. It seems hacky for me to have to attempt to prime the product by loading it up first and then it will be in the uow context. On this note I ran into another issue. Once I touch this item, it loads up the product into the context. If I then go and load the product using the aggregate, I would have thought that the object tracking would have replaced the non fully loaded entity on the order item with the newly loaded item. Or even better, I would have thought that the item would have been found in the uow context and would have been “completed” by the full product load. I hope this makes sense so far. As you can see there is a big potential for performance issues on that Product object associated to the order item. Even though an item in the uow has been fully populated, the item associated with the order item still makes queries to the database for all child properties called on the Product. I next tried something different, thinking that I could get around this issue. I knew the product id was 1 so I loaded this item up first and when the product property of the order item was touched, the item from the uow was associated and all is well. But wait there’s more… Next I thought, ok what happens if I put the same aggregate on the back reference to the product so I would not have to do some smart fetching. In the first test I performed, I did not load the product first I just loaded the order item with the “Populated” aggregate. All of the items were populated, even the product. I thought man this thing kicks ass, there must have been twenty different entities being populated. The next thing I did was to pre populate the product. When I load the order item, it does query the database for the product and it is returned but it was smart enough to associate the already loaded Product with the item. (I assume, I did not attempt to make any changes to the object to see if they were the same pointer) What I am asking here is: is there a best practice that you recommend for this scenario? Even though it is hacky, I don’t mind pre loading the products that are important. Should I go one step further and cache the products since they rarely change? If I do put them in the cache, will the system look there before they are loaded from the database? My second question is something that I may have done incorrectly while trying to figure out the above scenario. I pre loaded the product using two different mechanisms. retVal = mt.FindOne<Product>(query); And the Second method was My question is, should the engine have been smart enough in the same uow to determine you were attempting to obtain the same object since Id is the key? Two different queries were made against the database and what is interesting is the queries were not the same FindOne produced: exec sp_executesql N'SELECT and the second one produced: exec sp_executesql N'SELECT I am not sure if this is a problem, but I figured I would pass it on just in case. I love the product, it has been a huge time saver. I am using the 033009 nightly build. Joe Feser |
|
|
Aggregates are a feature of queries, not of entities. So, as you have found, an entity does not remember with what aggregates it was loaded, and does not propagate those aggregates when traversing an association. This would not really be possible since an entity could have been reached by several different routes over the course of a unit of work, potentially as part of a different aggregate each time. If your Product entities are expensive to load, then what you can do is specify the expensive properties to be lazy-loaded. That way, when you touch a Product through a lazy-loaded association, only the "cheap" properties will be loaded. To mark a property as lazy loaded, apply the EagerLoad(AggregateName="...") attribute to the field, or use the Aggregate Names property in the designer. LightSpeed will download properties that have been marked up in this way only when you touch those properties or if you explicitly load the entity with that aggregate name. Hope this makes sense -- see Advanced Querying in the help file for additional info, or post here if it still doesn't help. Caching is worthwhile if you have entities which are expensive to load but would otherwise be loaded frequently. Yes, LightSpeed will look in the cache rather than loading from the database if possible. You don't need to preload the cache unless the load stage is going to be really expensive if it happens while is user is using the system -- if an entity is marked for caching then it will automatically be cached once loaded. Regarding your second question, the reason these generate different SQL is that they have different semantics at the query level, even though you and I know that they'll end up doing the same thing. FindOne says "get me the one entity with Id such-and-such." First() says "get me the first entity with Id such-and-such," implying that there might be many such entities (even though you and I know there won't be), and it's okay if there are, we just want the first. So First() is more like a paging Find query with a page size of 1. The LINQ equivalent to FindOne is not First() but Single(), and if you use Single() I think you will see a query much more like the FindOne version. Hope this clarifies things -- do feel free to follow up on the aggregate stuff if this hasn't answered your question. |
|
|
Ivan, I'm fairly new to LightSpeed and I'm attempting to use the WithAggregate extension feature of LightSpeed based on the eager loading screen cast on the MindScape web site. However, the WithAggregate method is not available in intellisense. The code in the screen cast is something like the following: unitOfWork.Products.WithAggregate("AllSkus"); I essentially have an order with order items and I want to use the WithAggregate feature to load the order items collection. I've looked in the LightSpeed.chm help documenation but don't see anything in there with an example. Any help would be appreciated. Dave
|
|
|
Hi Dave, WithAggregate is an extension method, make sure you have using Mindscape.LightSpeed.Linq declared in your class file and that will bring the extension method into scope.
Jeremy |
|
|
With regards to the semantic difference between First and Single here... (I was just looking through the API documentation for this, but couldn't find it and am too lazy to keep looking, so I thought I'd ask): What would happen if FindOne<T> is given a query that returns more than one result? In LINQ, I expect .Single() or .SingleOrDefault() to throw an InvalidOperationException if more than one item matches the predicate - Does this happen in LS?
For clarity - I'm refactoring some code that looks like this:
var query = new Query {Identifier = entityId, AggregateName = "MarcelTheAggregate"}; var foo = unitOfWork.Find<Foo>(query).FirstOrDefault(); unitOfWork.Detach(foo); because if foo is null, .Detach() throws an exception. (I can tidy this up easily enough) Instinctively this looks to me like we're grabbing all the rows that match the query (yes, there should theoretically only be one that matches that ID, but stranger things have happened, right?) and using .FirstOrDefault on the resultant set to get the first one. Is it safe enough for me to refactor this to
var query = new Query {Identifier = entityId, AggregateName = "MarcelTheAggregate"}; var foo = unitOfWork.FindOne<Foo>(query); if (foo != null) unitOfWork.Detach(foo); Or does this rely on there being 0 or 1 rows in the database that match the query?
Thanks, Ian
|
|
|
FindOne behaves the same as SingleOrDefault but with a different exception thrown. FindOne will throw a LightSpeedException indicating a single result was expected if there is more than one result returned, and will return null if there are no results. So what you have currently (Find with a FirstOrDefault) is probably the most sensible way of expressing what you want since the FirstOrDefault will be performed on the in-memory EntityCollection returned from the Find call.
Jeremy |
|
|
Thanks :) |
|