【发布时间】:2012-06-13 07:28:00
【问题描述】:
考虑这些人为的实体对象:
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 的影响。它会在枚举消费者时触发一个查询,然后它会一个一个地抓取每个购买集合。这个问题的标准解决方案是添加一个包含:
var consumers = Consumers.Include("Purchases").Where(consumer => consumer.NeedsProcessed);
//assume that ProcessConsumers accesses the Consumer.Purchases property
SomeExternalServiceICannotModify.ProcessConsumers(consumers);
这在许多情况下都可以正常工作,但在某些复杂的情况下,包含可能会完全破坏性能几个数量级。有没有可能做这样的事情:
- 抓住我的消费者,var consumer = _entityContext.Consumers.Where(...).ToList()
- 获取我的购买,var purchase = _entityContext.Purchases.Where(...).ToList()
- 为消费者补充水分。从我已经加载到内存中的购买中手动购买集合。然后当我将它传递给 ProcessConsumers 时,它不会触发更多的数据库查询。
我不知道如何做#3。如果您尝试访问任何会触发延迟加载(以及因此 Select N+1)的 consumer.Purchases 集合。也许我需要将消费者转换为正确的类型(而不是 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();
}
编辑: 我已经稍微重写了这个例子,希望能更清楚地揭示这个问题。
【问题讨论】:
-
IIRC EF 将自动对集合进行水合,因此 #3 不必手动完成。
-
您的第一个查询应该作为单个 SQL 语句执行。您是否看到多个数据库调用?
-
@Nicholas,你是对的,我更新了示例以使其选择 N+1。这是一个非常简单的人为示例,请阅读整个问题并尝试理解我真正要问的内容。 .Include 不足的实际示例要复杂得多,放在 SO 问题中也不合理。
-
您绝对应该在第一个查询中删除 .ToList() 调用,因为这将导致它在执行下一行之前针对数据库执行。
-
@Kris 你错了,第一个查询调用的 .ToList() 阻止再次执行它,因为列表已加载到内存中。
标签: c# mysql entity-framework entity-framework-4 linq-to-entities