Yeg*_*sky 4 c# performance entity-framework
我们假设我有这些课程:
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}
}
Run Code Online (Sandbox Code Playgroud)
我想做这样的事情
foreach ( var car in db.Cars )
{
var x = car.Doors.Min(d => d.Weight);
}
Run Code Online (Sandbox Code Playgroud)
正如我在EFTraceLog中看到的那样,他从Doors中选择了Select*,其中CarId = @ ...并在应用服务器上计算"Min",而不是在db服务器上计算
我有很大的车和门桌,所以这个操作持续几分钟.但如果我改变代码
foreach ( var car in db.Cars )
{
var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);
}
Run Code Online (Sandbox Code Playgroud)
那几秒钟.
为什么会有这么大的差异以及如何解决它?这里的问题是编写起来要简单得多
var x = car.Doors.Min(d => d.Weight);
Run Code Online (Sandbox Code Playgroud)
然后
var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);
Run Code Online (Sandbox Code Playgroud)
更新
我们正在使用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;
Run Code Online (Sandbox Code Playgroud)
只有这一个很快
var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight);
Run Code Online (Sandbox Code Playgroud)
更新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]
Run Code Online (Sandbox Code Playgroud)
Linq to Entities只支持Min没有额外的投影.这意味着您Min将使用Linq To Objects调用当前(使您的Doors集合具体化 - 因此您在日志中看到的生成的SQL).
来自MSDN:
支持的:
TSource Min<TSource>(this IQueryable<TSource> source)
Run Code Online (Sandbox Code Playgroud)
不支持:
TResult Min<TSource, TResult>(this IQueryable<TSource> source,Expression<Func<TSource, TResult>> selector)
Run Code Online (Sandbox Code Playgroud)
你可以尝试使用OrderBy,Select和FirstOrDefault(即是支持),以达到同样的效果:
var min = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)
更新:
显然Entity Framework不支持Lazy-Load with Projection,这意味着无论何时加载引用的实体(Car.Doors在您的情况下)框架Lazy-Load所有数据,您都无法选择Weight要加载的属性(在您的情况下).
这就是两个电话之间差异的原因:
// Accessing 'Doors' thru 'Car' means 'Lazy Load'
car.Doors.Select(x => x.Weight).Min();
Run Code Online (Sandbox Code Playgroud)
但,
// No 'Lazy Load' involved, hence projection is possible
db.Doors.Where(x => x.CarId == carId).Select(x => x.Weight).Min();
Run Code Online (Sandbox Code Playgroud)
您还可以尝试访问Car.Doors和Eager Load Doors:
foreach ( var car in db.Cars.Include(x => x.Doors))
{
var x = car.Doors.Select(x => x.Weight).Min();
}
Run Code Online (Sandbox Code Playgroud)
(我使用了ServyMin提出的缩短替代方案)