考虑这些人为的实体对象:
public class Consumer
{
public int Id { get; set; }
public string Name { get; set; }
public bool NeedsProcessed { get; set; }
public virtual IList<Purchase> Purchases { get; set; } //virtual so EF can lazy-load
}
public class Purchase
{
public int Id { get; set; }
public decimal TotalCost { get; set; }
public int ConsumerId { get; set; }
}
现在假设我要运行此代码:
var consumers = Consumers.Where(consumer => consumer.NeedsProcessed);
//assume that ProcessConsumers accesses the Consumer.Purchases property
SomeExternalServiceICannotModify.ProcessConsumers(consumers);
默认情况下,这会在ProcessConsumers方法中遇到Select N + 1。它会在枚举消费者时触发查询,然后它会将每个购买集合1抓取1.此问题的标准解决方案是添加一个包含:
var consumers = Consumers.Include("Purchases").Where(consumer => consumer.NeedsProcessed);
//assume that ProcessConsumers accesses the Consumer.Purchases property
SomeExternalServiceICannotModify.ProcessConsumers(consumers);
在许多情况下,这种方法很好,但在某些复杂情况下,包含可以完全破坏性能数量级。有可能做这样的事情:
我不知道怎么做#3。如果您尝试访问任何consumer.Purchases集合,它将触发延迟加载(因此选择N + 1)。也许我需要将消费者转换为正确的类型(而不是EF代理类型),然后加载集合?像这样:
foreach (var consumer in Consumers)
{
//since the EF proxy overrides the Purchases property, this doesn't really work, I'm trying to figure out what would
((Consumer)consumer).Purchases = purchases.Where(x => x.ConsumerId = consumer.ConsumerId).ToList();
}
修改 我已经重新编写了一些例子,希望能更清楚地揭示这个问题。
答案 0 :(得分:1)
如果我理解正确,您希望在1个查询中加载已过滤的消费者子集,每个消费者都有一个已过滤的购买子集。如果这不正确,请原谅我对你的意图的理解。如果这是正确的,你可以这样做:
var consumersAndPurchases = db.Consumers.Where(...)
.Select(c => new {
Consumer = c,
RelevantPurchases = c.Purchases.Where(...)
})
.AsNoTracking()
.ToList(); // loads in 1 query
// this should be OK because we did AsNoTracking()
consumersAndPurchases.ForEach(t => t.Consumer.Purchases = t.RelevantPurchases);
CannotModify.Process(consumersAndPurchases.Select(t => t.Consumer));
请注意,如果Process函数希望修改使用者对象,然后将这些更改提交回数据库,则此方法无效。
答案 1 :(得分:0)
如果您使用相同的上下文来获取两个集合,EF将为您填充consumer.Purchases
个集合:
List<Consumer> consumers = null;
using ( var ctx = new XXXEntities() )
{
consumers = ctx.Consumers.Where( ... ).ToList();
// EF will populate consumers.Purchases when it loads these objects
ctx.Purchases.Where( ... ).ToList();
}
// the Purchase objects are now in the consumer.Purchases collections
var sum = consumers.Sum( c => c.Purchases.Sum( p => p.TotalCost ) );
编辑:
这导致只有2个db调用:1个用于获取Consumers
的集合,1个用于获取Purchases
的集合。
EF会查看返回的每条Purchase
条记录,并从Consumer
中查找相应的Purchase.ConsumerId
条记录。然后,它会将Purchase
对象添加到Consumer.Purchases
集合中。
选项2:
如果您有某种原因需要从不同的上下文中获取两个列表,然后将它们链接起来,我会在Consumer
类中添加另一个属性:
partial class Consumer
{
public List<Purchase> UI_Purchases { get; set; }
}
然后,您可以从Purchases
集合中设置此属性,并在您的用户界面中使用它。
答案 2 :(得分:0)
抓住我的消费者
var consumers = _entityContext.Consumers
.Where(consumer => consumer.Id > 1000)
.ToList();
抓取我的购物
var purchases = consumers.Select(x => new {
Id = x.Id,
IList<Purchases> Purchases = x.Purchases
})
.ToList()
.GroupBy(x => x.Id)
.Select( x => x.Aggregate((merged, next) => merged.Merge(next)))
.ToList();
水合消费者。从手动购买集合 购买我已经加载到内存中。
for(int i = 0; i < costumers.Lenght; i++)
costumers[i].Purchases = purchases[i];
答案 3 :(得分:0)
通过对数据库进行工作,您是否有可能解决多次往返或低效查询生成问题 - 主要是通过返回投影而不是特定实体,如下所示: / p>
var query = from c in db.Consumers
where c.Id > 1000
select new { Consumer = c, Total = c.Purchases.Sum( p => p.TotalCost ) };
var total = query.Sum( cp => cp.Total );
我无论如何都不是EF专家,所以如果这种技术不合适,请原谅我。