简体   繁体   中英

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?

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);
         }
     }
}

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)
              };

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)),

});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM