关于为啥这个 linq ef 核心查询不会执行的建议
Posted
技术标签:
【中文标题】关于为啥这个 linq ef 核心查询不会执行的建议【英文标题】:Suggestions on why this linq ef core query wont execute关于为什么这个 linq ef 核心查询不会执行的建议 【发布时间】:2021-12-23 03:44:41 【问题描述】:早上好,
如果我 not 在 where 子句中添加 createdtimestamp 的条件,我在 EF Core 中有以下查询。如果我添加其他条件子句来过滤数据,如下所示,查询将毫无问题地执行。
var q = (
from trckhead in DbContext.TrackingBatchHeader
from userdets in DbContext.UserDetails
.Where(u => u.UserId == trckhead.ClosedByUserId).DefaultIfEmpty()
from trckkeys in DbContext.TrackingBatchesItemKey
.Where(t => t.TrackingNo == trckhead.TrackingNo).DefaultIfEmpty()
from trcklink1 in DbContext.TrackingBatchesLink
.Where(x => x.TrackingNo == trckhead.TrackingNo && x.TrackingType == "I").DefaultIfEmpty()
from trcklink2 in DbContext.TrackingBatchesLink
.Where(x => x.TrackingNo == trckhead.TrackingNo && x.TrackingType == "T").DefaultIfEmpty()
join trckref in DbContext.TrackingBatchesReference on trckhead.TrackingBatchType equals trckref
.TrackingBatchType
join mthsite in DbContext.SiteDetail on trckhead.SiteCode equals mthsite.SiteCode
join userdets2 in DbContext.UserDetails on trckhead.CreatedByUserId equals userdets2.UserId
where
(string.IsNullOrEmpty(searchRequest.Status) || trckhead.Status == searchRequest.Status) &&
trckhead.CreatedTimestamp >= DateTime.Now.AddMonths(-11)
select new TrackingBatchSearchResultDto
TrackingBatchId = trckhead.TrackingNo,
SiteCode = trckhead.SiteCode,
TrackingBatchType = trckhead.TrackingBatchType,
Status = trckhead.Status,
Created = trckhead.CreatedTimestamp,
CreatedById = trckhead.CreatedByUserId,
ClosedById = trckhead.ClosedByUserId,
UserDescription = trckhead.UsersDescription,
TrackingBatchDescription = trckref.TrackingBatchTypeDescription,
SiteName = mthsite.Title,
CreatedByName = userdets2.FullUserName ?? string.Empty,
ClosedByName = userdets.FullUserName ?? string.Empty,
Link1 = trcklink1.TrackingData ?? string.Empty,
Link2 = trcklink2.TrackingData ?? string.Empty
).Distinct();
return await q.ToListAsync();
如果我在查询中保留日期子句,则引发的异常显示以下内容。如果我将一个函数传递给查询,但检查一个简单的日期,我会认为服务器评估不会很高兴。我可以在获得整个数据集后按日期过滤结果,但返回的记录数量可能很大,所以我宁愿从数据库查询中过滤掉它。任何帮助将不胜感激。
The LINQ expression 'DbSet<TrackingBatchHeader>
.SelectMany(
source: t => DbSet<UserDetail>
.Where(u => u.UserId == t.ClosedByUserId)
.DefaultIfEmpty(),
collectionSelector: (t, c) => new TransparentIdentifier<TrackingBatchHeader, UserDetail>(
Outer = t,
Inner = c
))
.SelectMany(
source: ti => DbSet<TrackingBatchesItemKey>
.Where(t0 => t0.TrackingNo == ti.Outer.TrackingNo)
.DefaultIfEmpty(),
collectionSelector: (ti, c) => new TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>(
Outer = ti,
Inner = c
))
.SelectMany(
source: ti0 => DbSet<TrackingBatchesLink>
.Where(t1 => t1.TrackingNo == ti0.Outer.Outer.TrackingNo && t1.TrackingType == "I")
.DefaultIfEmpty(),
collectionSelector: (ti0, c) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>(
Outer = ti0,
Inner = c
))
.SelectMany(
source: ti1 => DbSet<TrackingBatchesLink>
.Where(t2 => t2.TrackingNo == ti1.Outer.Outer.Outer.TrackingNo && t2.TrackingType == "T")
.DefaultIfEmpty(),
collectionSelector: (ti1, c) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>(
Outer = ti1,
Inner = c
))
.Join(
outer: DbSet<TrackingBatchesReference>,
inner: ti2 => ti2.Outer.Outer.Outer.Outer.TrackingBatchType,
outerKeySelector: t3 => t3.TrackingBatchType,
innerKeySelector: (ti2, t3) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>, TrackingBatchesReference>(
Outer = ti2,
Inner = t3
))
.Join(
outer: DbSet<SiteDetail>,
inner: ti3 => ti3.Outer.Outer.Outer.Outer.Outer.SiteCode,
outerKeySelector: s => s.SiteCode,
innerKeySelector: (ti3, s) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>, TrackingBatchesReference>, SiteDetail>(
Outer = ti3,
Inner = s
))
.Join(
outer: DbSet<UserDetail>,
inner: ti4 => ti4.Outer.Outer.Outer.Outer.Outer.Outer.CreatedByUserId,
outerKeySelector: u0 => u0.UserId,
innerKeySelector: (ti4, u0) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>, TrackingBatchesReference>, SiteDetail>, UserDetail>(
Outer = ti4,
Inner = u0
))
.Where(ti5 => ti5.Outer.Outer.Outer.Outer.Outer.Outer.Outer.CreatedTimestamp >= (Nullable<DateTime>)DateTime.Now.AddMonths(-11))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
【问题讨论】:
不要写这样的查询。 LINQ 不是 SQL 的替代品,EF Core 也不是数据访问库。 DbContext 不是数据库的模型。 EF Core 是一个对象到关系映射器。 EF Core 将根据对象之间的关系生成 SQL 查询。JOIN
在 LINQ 查询中不应该有任何理由。至于Where
子句,不需要像string.IsNullOrEmpty(searchRequest.Status)
这样的包罗万象的技巧。您可以有条件地添加过滤器,例如if (status!=null) query=query.Where(t=>t.Status == status);
。
如果您的 DbContext 和实体配置正确,您将不需要任何这些连接。如果您在 Select()
子句中使用简单的对象访问表示法,LINQ 会将其转换为必要的 JOIN 并在 SELECT
子句中添加必要的字段
异常与您发布的查询不匹配。你能简化查询并发布实际的查询和异常吗?
@K4E 你应该。因为这就是简化查询和消除可能导致问题的代码所需要的。在那之后,包罗万象的条款是另一件可能导致问题的事情。 String.IsNullOrWhitespace
无法转换为 SQL。没有这样的功能。它甚至不必在查询中,因为它所做的只是禁用下一个子句。您可以链接.Where()
调用,结果等效于AND
。这就是为什么使用if(status==null) q=q.Where(...);
可以有条件地添加过滤器
@K4E 请忽略一般 cmets,因为它们与问题无关。您使用的是什么 EF Core 版本?因为最新的 EFC 5 / 6 在这种情况下会显示更好的错误消息,其中包含更多可能导致错误的提示。据我所知,问题要么是DateTime.Now.AddMonths(-11)
,我很确定它是可翻译的,要么很可能是未映射实体类的CreatedTimestamp
属性(通过[NotMapped]
或.Ignore
)。
【参考方案1】:
简而言之,忽略为实体之间的关系建立导航属性,而是使用“from”手动外连接加上 Distinct
对格式不正确的查询添加终极侮辱,试图像锤子一样使用 EF一个方钉穿过一个圆孔。
有了适当的关系,该查询应该是微不足道的。
您的日期时间条件错误消息与您的查询不匹配。
** 编辑:删除了有关日期比较的详细信息 - 这似乎没有直接关系,因为我确实验证了可空日期/日期时间比较确实适用于 EF Core 中的.AddMonth()
。
扩展条件逻辑有助于简化正在执行的查询。
例如:
var query = (/*build your base query*/);
if (!string.IsNullOrEmpty(searchRequest.Status))
query = query.Where(x => x.Status == searchRequest.Status);
if (searchRequest.DateFrom.HasValue)
var fromDate = searchRequest.DateFrom.Value.AddMonths(-11);
query = query.Where(x => x.CreatedTimestamp >= fromDate);
var results = query.Select(...).Distinct().ToList();
只要您提供足够的信息来正确执行此操作,EF 就可以执行一些魔术来构建查询。看到它构建类似ti5.Outer.Outer.Outer.Outer.Outer.Outer.Outer.CreatedTimestamp
的东西应该会敲响警钟。您的查询中可能有一些潜伏的东西正在触发客户端评估,或者只是连接等的组合已经超过了复杂性限制或暴露了 EF Core 中的错误。出发点是通过利用导航属性而不是连接来简化查询表达式,并尽可能将条件逻辑外部化。
如果您别无选择,只能尝试查询您不准备使用导航属性正确映射的关系,我建议您探索在 SQL 中将查询构建为存储过程,然后定义一个可以映射到的实体该 Sproc 的结果记录。
【讨论】:
EF Core 与您习惯的 EF6 完全不同。虽然我也更喜欢/推荐导航属性而不是手动连接,但我绝不会建议使用 SP/原始 SQL 进行简单的连接查询。您提到的其他内容也不适用于 EFC - 条件Where
不是强制性的,因为 EFC 消除了常量谓词(如您所见,错误表达式中没有 searchRequest.Status
条件)。同样根据他们使用的错误DateTime.Now.AddMonths(-11)
,这是完全可翻译的。所以你不知道具体的问题是什么,只是猜测。
当表达式访问者无法替换表达式时,我不喜欢 EF Core 抛出“无法翻译”的方式。虽然这对于 EF Core 团队来说可能更容易编写,但它无法提供有关失败发生原因的任何线索。您可能对日期表达式是正确的,但可能不是。
感谢 cmets,我将全员参与并向团队报告。我在发布查询应该包含 DateTime.Now.AddMonths(-11) 而不是来自 searchrequest 的代码时确实犯了一个错误。
在验证 DateTime 操作和与可为空的 Date 或 DateTime 属性的比较可能不是直接责任后,我已经更新了答案,尽管奇怪的是,删除该条件似乎允许查询跑步。我确实验证了这些操作确实适用于 EF Core 3.1 和 5。如果不解决某种形式的复杂性问题,但减少表面积以寻找潜在问题,该解决方案可能会涉及大幅简化查询。以上是关于关于为啥这个 linq ef 核心查询不会执行的建议的主要内容,如果未能解决你的问题,请参考以下文章
为啥 LINQ 在我的查询中使用错误的数据类型,而它在 EF 架构中被正确声明?
Linq WHERE EF.Functions.Like - 为啥直接属性起作用而反射不起作用?