4

假设我有这些课程:

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 = @... 的 Doors 中选择 * 并在应用程序服务器上计算“Min”,而不是在 db 服务器上

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

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

更新

我们正在使用实体框架 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]
4

2 回答 2

5

Linq to Entities只支持Min没有额外的投影。这意味着您的 currentMin将使用 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)

您可以尝试使用OrderBy,SelectFirstOrDefault支持的)来实现相同的结果:

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

(我使用了ServyMin提出的缩短的替代方案)

于 2013-10-03T15:22:55.407 回答
0

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

var minWeight = db.Doors.Select(door => door.Weight).Min();
于 2013-10-03T15:44:40.153 回答