Prefetching in Linq

Posts   
 
    
rsiera
User
Posts: 48
Joined: 09-Oct-2011
# Posted on: 17-Aug-2015 06:49:33   

Consider this code:


            using (var adapter = db.GetDbAdapter())
            {
                var pathEdge = new PathEdge<OrderEntity>(CustomerEntity.PrefetchPathOrders);
                var iquery = (IQueryable<CustomerEntity>)new LinqMetaData(adapter).GetQueryableForEntity<CustomerEntity>();
                List<CustomerEntity> result1 = iquery.WithPath(pathEdge).ToList();
                IEnumerable<CustomerDto> result2 = iquery.WithPath(pathEdge).ToList().Select(e => new CustomerDto(e));
                List<CustomerDto> result3 = iquery.WithPath(pathEdge).Select(e => new CustomerDto(e)).ToList();
            }

            public class CustomerDto 
            {
                public CustomerDto()
                {
                    Orders = new List<OrderDto>();
                }

                public CustomerDto(CustomerEntity lqoCustomer)
                {
                    CustomerId = lqoCustomer.CustomerId;
                    CustomerName = lqoCustomer.CustomerName;
                    Website = lqoCustomer.Website;
                    Orders = new List<OrderDto>();
                    foreach (var Order in lqoCustomer.Orders) //<<==  lqoCustomer.Orders is empty in result3
                    {
                        Orders.Add(new OrderDto(Order));
                    }
                }

                public int CustomerId { get; set; }
                public string CustomerName { get; set; }
                public string Website { get; set; }
                public List<OrderDto> Orders { get; set; }
            }           

result1 contains a list of CustomerEntities with correctly hydrated Orders collection. result2 contains a IEnumerable with CustomerDto's with correctly hydrated Orders collection. But in result3 the CustomerDto have an empty Orders collection.

My question: Is this by design? Is there another way to get method 3 working correctly?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 17-Aug-2015 16:35:27   

It doesn't work because you have imperative code that is run in-memory (not in the db) yet it works on related data (which isn't there) as prefetch paths are only fetched as the outside of a query. Your 3rd query doesn't have an entity query as its outer projection (but a custom dto projection) so it's not going to fetch the prefetch path.

To do this, I think you could use a nested query in the projection: (not tested code)


var q = from c in metaData.Customer
        select new CustomerDto()
            {
                CustomerId = e.CustomerId,
                CustomerName = e.CustomerName,
                ..
                Orders = from o in c.Orders
                         select new OrderDto(o)
            }

That of course doesn't add the elements to an existing collection, but creates a new one from teh elements in the nested query.

It's tricky though: the nested query must be related to the outer query.

Example of working query:

LinqMetaData metaData = new LinqMetaData(adapter);
var q = from c in metaData.Customer
        where c.CustomerId == "ALFKI"
        select new
        {
            c.CustomerId,
            OrderData = c.Orders
                            .Select(o => new 
                                        { 
                                            o.OrderId, 
                                            OrderDetailData = o.OrderDetails 
                                        })
        };

Frans Bouma | Lead developer LLBLGen Pro
rsiera
User
Posts: 48
Joined: 09-Oct-2011
# Posted on: 24-Aug-2015 08:23:22   

Otis wrote:

It doesn't work because you have imperative code that is run in-memory (not in the db) yet it works on related data (which isn't there) as prefetch paths are only fetched as the outside of a query. Your 3rd query doesn't have an entity query as its outer projection (but a custom dto projection) so it's not going to fetch the prefetch path.

Thanks for explaining that. I assumed it was something in that direction.

I refactored my code to avoid this situation.