Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework - Selective Condition on Included Navigation Property

Assume I have these simplified EF generated entities...

public class PurchaseOrder
{
     public int POID {get;set;}
     public int OrderID {get;set;}
     public int VendorID {get;set;}
     public IEnumerable<Order> Orders {get;set;}
}

public class Order
{
     public int OrderID {get;set;}
     public decimal Price {get;set;}
     public IEnumerable<Item> Items {get;set;}
}

public class Item
{
     public int OrderID {get; set;}
     public string SKU {get;set;}
     public int VendorID {get;set;}
     public Order Order {get;set;}
}

Business Logic:

An order can have multiple POs, one for each distinct vendor on the order (vendors are determined at the Item level).

How Can I selectively Include Child Entities?

When querying for POs, I want to automatically include child entites for Order and Item.

I accomplish this, using Include()...

Context.PurchaseOrders.Include("Orders.Items");

This does it's job and pulls back related entities, but, I only want to include Item entities whose VendorID matches the VendorID of the PurchaseOrder entity.

With traditional SQL, I'd just include that in the JOIN condition, but EF builds those internally.

What LINQ magic can I use tell EF to apply the condition, without manually creating the JOINs between the entities?

like image 295
ctorx Avatar asked Nov 30 '11 22:11

ctorx


3 Answers

You can't selectively pull back certain child entities that match a certain condition. The best you can do is manually filter out the relevant orders yourself.

public class PurchaseOrder
{
     public int POID {get;set;}
     public int OrderID {get;set;}
     public int VendorID {get;set;}
     public IEnumerable<Order> Orders {get;set;}

     public IEnumerable<Order> MatchingOrders {
         get {
            return this.Orders.Where(o => o.VendorId == this.VendorId);
         }
     }
}
like image 78
Adam Rackis Avatar answered Oct 17 '22 00:10

Adam Rackis


You can't. EF doesn't allow conditions for eager loading. You must either use multiple queries like:

var pos = from p in context.PurchaseOrders.Include("Order")
          where ...
          select p;
var items = from i in context.Items
            join o in context.Orders on new { i.OrderId, i.VendorId} 
               equals new { o.OrderId, o.PurchaseOrder.VendorId }
            where // same condition for PurchaseOrders
            select i;

Or you can use projection in single query:

var data = from o in context.Orders
           where ...
           select new
              {
                  Order = o,
                  PurchaseOrder = o.PurchaseOrder,
                  Items = o.Items.Where(i => i.VendorId == o.PurchaseOrder.VendorId)
              };
like image 30
Ladislav Mrnka Avatar answered Oct 17 '22 01:10

Ladislav Mrnka


You could use the IQueryable-Extensions here:

https://github.com/thiscode/DynamicSelectExtensions

The Extension builds dynamically an anonymous type. This will be used for projection as described by @Ladislav-Mrnka.

Then you can do this:

var query = query.SelectIncluding( new List<Expression<Func<T,object>>>>(){

//Example how to retrieve only the newest history entry
x => x.HistoryEntries.OrderByDescending(x => x.Timestamp).Take(1),

//Example how to order related entities
x => x.OtherEntities.OrderBy(y => y.Something).ThenBy(y => y.SomeOtherThing),

//Example how to retrieve entities one level deeper
x => x.CollectionWithRelations.Select(x => x.EntityCollectionOnSecondLevel),

//Of course you can order or subquery the deeper level
//Here you should use SelectMany, to flatten the query
x => x.CollectionWithRelations.SelectMany(x => x.EntityCollectionOnSecondLevel.OrderBy(y => y.Something).ThenBy(y => y.SomeOtherThing)),

});
like image 40
thiscode Avatar answered Oct 17 '22 02:10

thiscode