Entity Framework Core:使用包含关系表的LINQ建议需要改进的方法

18
我对Entity Framework Core和使用LINQ有一个问题。我想在访问"客户"表时获取其他表的详细信息。我可以使用下面的代码获得它们。总共有大约10个我需要连接的表,在这种情况下,下面的方法是否好或者还有更好的方法呢?"ClientId"是所有表的外键。

实际上,我收到以下警告

[09:34:33 警告] Microsoft.EntityFrameworkCore.Query 编译了一个查询,该查询通过“Include”或通过投影加载多个集合导航的相关集合。但没有配置“QuerySplittingBehavior”。默认情况下,Entity Framework将使用“QuerySplittingBehavior.SingleQuery”,这可能会导致查询性能缓慢。有关更多信息,请参见https://go.microsoft.com/fwlink/?linkid=2134277。要识别触发此警告的查询,请调用“ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))”

代码:

var client = await _context.Clients
                .Include(x => x.Address)
                .Include(x => x.Properties)
                .Include(x => x.ClientDetails)
                -------------------
                -------------------
                -------------------
                -------------------
                .Where(x => x.Enabled == activeOnly && x.Id == Id).FirstOrDefaultAsync();

优化查询的第一步可能是找出在SQL中如何最好地实现相同的结果。然后,您可以编写适合的LINQ到实体代码来匹配它。 - jmcilhinney
1
只需添加 AsSplitQuery()。没有使用此运算符加载这种数据将浪费资源。警告是完全正确的。已回答类似问题。关于性能问题的说明请参见 https://stackoverflow.com/a/67243921/10646316。 - Svyatoslav Danyliv
2
什么是“更好”?我们不知道这个查询的目的。 - Gert Arnold
最好的部分是,如果您将其配置为始终使用其中之一,它仍会发出警告。 - Captain Prinny
@CaptainPrinny 真的吗?-_- 我想知道是否可以在不抛出异常的情况下记录查询。我不想要真正的异常。我只是好奇。 - user15716642
2个回答

16

实际上,当您使用急加载(使用include())时,它会使用左连接(在一个查询中获取所有需要的查询)来获取数据。这是ef 5中默认的ef行为。 您可以在查询中设置AsSplitQuery()以将所有包含项拆分为单独的查询,例如:

var client = await _context.Clients
            .Include(x => x.Address)
            .Include(x => x.Properties)
            .Include(x => x.ClientDetails)
            -------------------
            -------------------
            -------------------
            -------------------
            .Where(x =>x.Id == Id).AsSplitQuery().FirstOrDefaultAsync()

这种方法需要更多的数据库连接,但这并不是非常重要的。作为最终建议,我建议在查询高性能时使用AsNoTracking()


2
我认为如果没有使用 AsSplitQuery(),则需要使用 AsNoTrackingWithIdentityResolution(),否则它会多次映射相同的实体。但是我不知道使用 AsSplitQuery() 是否会有相同的情况发生。 - Xriuk

1
我有三种不同的方法,具体取决于您使用的EF Core版本。
EF Core 5 - 正如一些之前的回答中提到的那样,有一个新的调用,它会将查询简单地分解为较小的子查询,并在最后映射所有关系。
/*rest of the query here*/.AsSplitQuery();

如果您无法直接迁移EF版本,则仍然可以手动拆分查询。
var client = await _context.Clients.FirstOrDefaultAsync(t => t.Enabled /*other conditions*/);

var Address = await _context.Addresses.FirstOrDefaultAsync(t => t.ClientId == client.Id);

/// Because they are tracked EF's entitytracker can under the hood 
/// map the sub queries to their correct relations 
/// in this case you should not use .AsNoTracking() 
/// unless you would want to stitch relations together yourself

另一种选择是将查询编写为 Select 语句。这样可以大大提高性能,但构建过程可能会更加麻烦。
var clientResult = await _context.Clients.Where(x => x.Id == id).Select(x => new 
{
 client = x, 
 x.Address,
 Properties = x.Properties.Select(property => new 
                    { 
                        property.Name /*sub query for one to many related*/ 
                    }).ToList(), 
 x.ClientDetails
}).ToListAsync();

仅使用少量的包含就可以创建笛卡尔爆炸

您可以在此文章中了解更多有关这个问题的信息

EF Core中的笛卡尔爆炸

优化性能的参考链接可以在此找到

最大化Entity Framework Core查询性能

选择变体会导致笛卡尔爆炸,相信我。目前没有使用AsSplitQuery与自定义投影的选项。 - Svyatoslav Danyliv
嗨,Svyatoslav。 我没有说选择不会导致笛卡尔爆炸,但在底层执行起来比.include更好。但是,如果以错误的方式执行任何类型的连接,无论是交叉、左连接还是外部左连接,都会导致笛卡尔爆炸。但是,不同的情况可能需要不同的解决方案。 - Bjarke Handsdal
抱歉,我不理解那个评论。在select语句中可以使用First或Default。而且select语句并不是选择一个表,而是从一个表中进行选择。 - Bjarke Handsdal
如果你在 Select 操作符内部使用 FirstOrDefault,EF 可能会生成 OUTER APPLY JOIN(速度也不快)。如果你在 Select 中选择了许多记录,则会出现笛卡尔爆炸。 - Svyatoslav Danyliv
1
实际上,“AsSplitQuery”是社区投票的结果。EF团队已经从EF Core 2.x中进行了重大更改,删除了默认行为的查询拆分功能。 - Svyatoslav Danyliv
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接