【问题标题】:How to make EntityFramework Relation IQueryable?如何使EntityFramework Relation IQueryable?
【发布时间】:2013-10-03 14:54:19
【问题描述】:

假设我有这些类:

public class Car
{
  public int CarId { get; set} 
  public virtual ICollection<Door> Doors { get; set} 
}

public class Door
{
   public int DoorId { get; set} 
   public decimal Weight { get; set} 
   public int CarId { get; set} 
}

我想做这样的事情

foreach ( var car in db.Cars )
{
   var x = car.Doors.Min(d => d.Weight);
}

正如我在 EFTraceLog 中看到的,他做了类似的事情 从 CarId = @... 的门中选择 * 并在应用程序服务器上计算“Min”,而不是在数据库服务器上

我有很大的汽车和门桌,所以这个操作会持续几分钟。 但是如果我把代码改成这个

foreach ( var car in db.Cars )
{
   var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);
}

然后是几秒钟。

为什么会有这么大的差异以及如何解决? 这里的问题是写起来要简单得多

var x = car.Doors.Min(d => d.Weight);

然后

var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);

更新

我们使用的是 Entity Framework 5.0

更新 2

我试过这些变种,它们很慢

var x = car.Doors.Select(door => door.Weight).Min();
var x = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).FirstOrDefault();
var x = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).First();
var x = car.Doors.OrderBy(x => x.Weight).FirstOrDefault().Weight;
var x = car.Doors.OrderBy(x => x.Weight).First().Weight;

只有这个快

var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);

更新 3

最佳查询产生此 sql

declare @p__linq__0 Int32 = cast(N'204' as Int32);

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
MIN([Extent1].[Weight]) AS [A1]
FROM [dbo].[Doors] AS [Extent1]
WHERE [Extent1].[CarId] = @p__linq__0
) AS [GroupBy1]

【问题讨论】:

  • Where(你的最后一行)语句生成什么 SQL?

标签: c# performance entity-framework


【解决方案1】:

Linq to Entities 只支持Min 没有额外的投影。这意味着您当前的 Min 将使用 Linq To Objects 调用(导致您的 Doors 集合实现 - 因此您在日志中看到生成的 SQL)。

来自MSDN

支持:

TSource Min<TSource>(this IQueryable<TSource> source)

不支持:

TResult Min<TSource, TResult>(this IQueryable<TSource> source,Expression<Func<TSource, TResult>> selector)

您可以尝试使用OrderBySelectFirstOrDefault支持)来达到相同的效果:

var min = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).FirstOrDefault();

更新:

显然Entity Framework 不支持带有投影的延迟加载,这意味着每当您加载引用的实体(在您的情况下为Car.Doors)时,框架延迟加载它的所有数据并且您不能选择哪个属性( Weight 在你的情况下)加载。

这就是两次调用之间存在差异的原因:

// Accessing 'Doors' thru 'Car' means 'Lazy Load'
car.Doors.Select(x => x.Weight).Min();

但是,

// No 'Lazy Load' involved, hence projection is possible
db.Doors.Where(x => x.CarId == carId).Select(x => x.Weight).Min();

您也可以尝试访问Car.DoorsEager Load Doors

foreach ( var car in db.Cars.Include(x => x.Doors))
{
   var x = car.Doors.Select(x => x.Weight).Min();
}

(我使用 Servy 建议的 Min 的缩写替代)

【讨论】:

  • 我没办法验证,所以我已经删除了。
  • 我试过 car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).First();还有 car.Doors.OrderBy(x => x.Weight).First().Weight。两者都没有帮助。仍然是相同的查询:Select * from Doors where CarId = @...
  • @YegorRazumovsky 这与向您建议的查询不同。 EF 不支持First,只支持FirstOrDefault
  • 试车.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).FirstOrDefault();同样的故事
  • 我也修改了答案。
【解决方案2】:

根据haim770's answer,EF 不支持进行投影的Min 的重载,但您可以先使用Select 执行投影以获得所需的结果:

var minWeight = db.Doors.Select(door => door.Weight).Min();

【讨论】:

  • @YegorRazumovsky 听起来您的查询提供程序配置不正确。 any 查询是否得到正确翻译?该特定表上的其他查询是否被正确翻译?
  • 这个很好用 var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);
  • @YegorRazumovsky 那翻译成什么 SQL?
  • @YegorRazumovsky 另外,请确保 Weight 是索引列。可能只是因为您拥有的数据量很大,如果它不是索引,则找到最小权重是一项昂贵的操作。显然,即使一切正常,对 ID 进行过滤也会更快;这不是一个适当的比较。问题是它是否在每种情况下都正确地翻译成 SQL。
  • 权重列有索引
猜你喜欢
  • 2012-10-31
  • 1970-01-01
  • 1970-01-01
  • 2015-11-14
  • 2020-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多