删除选择N + 1而不包含.Include

man*_*u08 7 c# mysql linq-to-entities entity-framework entity-framework-4

考虑这些人为的实体对象:

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; }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们说我想运行这段代码:

var consumers = Consumers.Where(consumer => consumer.NeedsProcessed);

//assume that ProcessConsumers accesses the Consumer.Purchases property
SomeExternalServiceICannotModify.ProcessConsumers(consumers);
Run Code Online (Sandbox Code Playgroud)

默认情况下,这会在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);
Run Code Online (Sandbox Code Playgroud)

在许多情况下,这种方法很好,但在某些复杂情况下,包含可以完全破坏性能数量级.有可能做这样的事情:

  1. 抓住我的消费者,var consumers = _entityContext.Consumers.Where(...).ToList()
  2. 抓取我的购买,var purchases = _entityContext.Purchases.Where(...).ToList()
  3. 为消费者提供水合.从我已加载到内存中的购买中手动购买集合.然后,当我将它传递给ProcessConsumers时,它不会触发更多的数据库查询.

我不知道怎么做#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();
}
Run Code Online (Sandbox Code Playgroud)

编辑: 我已经重新编写了一些示例,希望能更清楚地揭示问题.

Nic*_*ler 0

consumer.Purchases如果您使用相同的上下文来获取两个集合,EF 将为您填充集合:

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 ) );
Run Code Online (Sandbox Code Playgroud)

编辑 :

这仅导致 2 次数据库调用:1 次获取 的集合Consumers,1 次获取 的集合Purchases

EF 将查看返回的每条记录并从 中Purchase查找相应的记录。然后它会为您将该对象添加到集合中。ConsumerPurchase.ConsumerIdPurchaseConsumer.Purchases


选项2:

如果出于某种原因您想从不同的上下文中获取两个列表然后链接它们,我会向类添加另一个属性Consumer

partial class Consumer
{
  public List<Purchase> UI_Purchases { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以从集合中设置此属性Purchases并在 UI 中使用它。