编写LINQ查询,连接VS导航属性

rvpgvaaj  于 2023-04-09  发布在  其他
关注(0)|答案(1)|浏览(110)

我正在尝试更多地了解Linq查询和Entity Framework(4.1)。请看下面两个查询。这两个查询都返回汽车类型名称(CarType.Name)
在第一个查询中,我使用了join并忽略了导航属性CarType

from c in Cars.AsEnumerable().
Where(e => e.CarId == Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224"))
join ct in CarTypes on c.CarTypeId equals ct.CarTypeId
select new CarType {
    Name = ct.Name
}

在第二个例子中,我使用了导航属性CarType

from c in Cars.AsEnumerable()
where c.CarId == Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224")
select new CarType {
    Name = c.CarType.Name
}

我在LinqPad中运行了这两个函数,因此有Guid.Parse函数。
当我运行这些语句时,第一个语句运行得更快。LinqPad报告00:00:036。第二个语句运行得更慢,LinqPad报告00:00:103

从结果来看,使用连接而不是导航属性的Linq查询似乎更快。真的是这样吗?请有人对此进行一些说明。是否有任何一般指导,编写Linq查询时应该遵循的最佳实践?

谢谢

vuktfyat

vuktfyat1#

由于您正在调用.AsEnumerable(),因此查询不是使用LINQ to Entities计算的,而是使用LINQ to Objects计算的。
这意味着第一个可能会进行两次往返:一个用于拉取所有的汽车,另一个用于拉取所有的汽车类型。然后它在本地执行连接,使用LINQ to Objects用于此类操作的任何算法。
第二个可能是N + 1次往返,其中N是CarType的数量。您执行一次往返以获取所有汽车,然后每次这些汽车中的一辆具有实体框架尚未加载的CarTypeId时,它都会返回数据库以选择该CarType。
如果使用LINQPad中的SQL选项卡,则可以看到程序正在执行的所有LINQ查询。
在这种情况下,您应该应用的最佳实践是不要在Entity Framework对象集上调用.AsEnumerable()。相反,编写整个查询,然后在最后调用.ToList()以捕获结果。您可能会调用.AsEnumerable()作为解决方案,因为Guid.Parse()在LINQ to Entities查询中不起作用。但是你可以很容易地从查询中删除这部分。在LINQPad中,按Ctrl-2切换到C#语句模式,然后运行如下查询:

var guid = Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224");
var carTypeNames = 
    (from c in Cars
    where c.CarId == guid
    select new CarType {
        Name = c.CarType.Name
    }).ToList();
carTypeNames.Dump();

给出的两个查询在正确执行时应该具有大致相同的性能,因此您应该首选Navigation Properties,因为它们更简洁,更易于阅读。或者,根据您的喜好,您可以将查询转换为基于CarType集合的查询:

var guid = Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224");
var carTypeNames = 
    (from ct in CarTypes
    where ct.Cars.Any(c => c.CarId == guid)
    select new CarType {
        Name = c.CarType.Name
    }).ToList();
carTypeNames.Dump();

更新

避免像这样创建实体对象:

public class CarTypeSummary
{
    public string Name{get;set;}
}

void Main()
{
    var guid = Guid.Parse("0501cc96-5610-465d-bafc-16b30890c224");
    var carTypeNames = 
        (from ct in CarTypes
        where ct.Cars.Any(c => c.CarId == guid)
        select new CarTypeSummary {
            Name = c.CarType.Name
        }).ToList();
    carTypeNames.Dump();
}

在生产代码中,将API与底层数据类型解耦通常是一个好主意,这样可以给予您更灵活地更改内容,而无需在任何地方修改代码。

public interface ICarTypeSummary{string Name{get;}}
public class CarTypeSummary : ICarTypeSummary
{
    public string Name{get;set;}
}
public ICarTypeSummary GetCarTypeSummaryForCar(Guid guid) 
{
    return (from ct in CarTypes
            where ct.Cars.Any(c => c.CarId == guid)
            select new CarTypeSummary {
                Name = c.CarType.Name
            }).FirstOrDefault();
}

这样,如果你将来决定只返回一个实际的CarType,以利用Entity Framework的缓存机制,你可以在不干扰API的情况下更改你的实现:

// Make the Entity class implement the role interface
public partial class CarType : ICarTypeSummary {}

public ICarTypeSummary GetCarTypeSummaryForCar(Guid guid) 
{
    return CarTypes.FirstOrDefault(
        ct => ct.Cars.Any(c => c.CarId == guid));
}

相关问题