gpt4 book ai didi

c# - Linq to 实体扩展方法内部查询 (EF6)

转载 作者:太空狗 更新时间:2023-10-29 21:26:38 25 4
gpt4 key购买 nike

有人可以向我解释为什么 EF 引擎在以下情况下会失败吗?

它适用于以下表达式:

var data = context.Programs
.Select(d => new MyDataDto
{
ProgramId = d.ProgramId,
ProgramName = d.ProgramName,
ClientId = d.ClientId,
Protocols = d.Protocols.Where(p => p.UserProtocols.Any(u => u.UserId == userId))
.Count(pr => pr.Programs.Any(pg => pg.ProgramId == d.ProgramId))
})
.ToList();

但是如果我将一些封装到扩展方法中:

public static IQueryable<Protocol> ForUser(this IQueryable<Protocol> protocols, int userId)
{
return protocols.Where(p => p.UserProtocols.Any(u => u.UserId == userId));
}

结果查询:

var data = context.Programs
.Select(d => new MyDataDto
{
ProgramId = d.ProgramId,
ProgramName = d.ProgramName,
ClientId = d.ClientId,
Protocols = d.Protocols.ForUser(userId)
.Count(pr => pr.Programs.Any(pg => pg.ProgramId == d.ProgramId))
})
.ToList();

异常失败:LINQ to Entities 无法识别方法“System.Linq.IQueryable1[DAL.Protocol] ForUser(System.Linq.IQueryable1[DAL.Protocol], Int32)”方法,并且此方法不能翻译成商店表达式。

我希望 EF 引擎构建整个表达式树,链接必要的表达式,然后生成 SQL。它为什么不这样做?

最佳答案

发生这种情况是因为调用了 ForUser()在 C# 编译器看到您传递给 Select 的 lambda 时构建的表达式树中生成。 Entity Framework 试图弄清楚如何将该函数转换为 SQL,但由于一些原因它无法调用该函数(例如 d.Protocols 目前不存在)。

适用于这种情况的最简单方法是让您的助手返回一个条件 lambda 表达式,然后将其传递到 .Where()方法自己:

public static Expression<Func<Protocol, true>> ProtocolIsForUser(int userId)
{
return p => p.UserProtocols.Any(u => u.UserId == userId);
}

...

var protocolCriteria = Helpers.ProtocolIsForUser(userId);
var data = context.Programs
.Select(d => new MyDataDto
{
ProgramId = d.ProgramId,
ProgramName = d.ProgramName,
ClientId = d.ClientId,
Protocols = d.Protocols.Count(protocolCriteria)
})
.ToList();

更多信息

当您在表达式树外部调用 LINQ 方法时(就像您对 context.Programs.Select(...) 所做的那样),Queryable.Select()扩展方法实际上被调用,它的实现返回一个 IQueryable<>表示在原始 IQueryable<> 上调用扩展方法.下面是 Select 的实现,例如:

    public static IQueryable<TResult> Select<TSource,TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector) {
if (source == null)
throw Error.ArgumentNull("source");
if (selector == null)
throw Error.ArgumentNull("selector");
return source.Provider.CreateQuery<TResult>(
Expression.Call(
null,
GetMethodInfo(Queryable.Select, source, selector),
new Expression[] { source.Expression, Expression.Quote(selector) }
));
}

当可查询的提供者必须从 IQueryable<> 生成实际数据时,它分析表达式树并试图弄清楚如何解释这些方法调用。 Entity Framework 内置了 many LINQ-related functions 的知识喜欢.Where().Select() ,因此它知道如何将这些方法调用转换为 SQL。但是,它不知道如何处理您编写的方法。

那么为什么这样做有效呢?

var data = context.Programs.ForUser(userId);

答案是你的ForUser方法未像 Select 那样实现上面的方法:您没有向可查询对象添加表达式来表示调用 ForUser .相反,您将返回 .Where() 的结果称呼。来自IQueryable<>的视角,就好像Where()被直接调用,调用ForUser()从未发生过。

您可以通过捕获 Expression 来证明这一点IQueryable<> 上的属性(property):

Console.WriteLine(data.Expression.ToString());

...这将产生这样的东西:

Programs.Where(u => (u.UserId == value(Helpers<>c__DisplayClass1_0).userId))

没有调用 ForUser()该表达式中的任何位置。

另一方面,如果您包含 ForUser()像这样在表达式树内部调用:

var data = context.Programs.Select(d => d.Protocols.ForUser(id));

... 然后是 .ForUser()方法实际上从未被调用,因此它永远不会返回 IQueryable<>知道 .Where()方法被调用。相反,可查询 的表达式树显示 .ForUser()被调用。输出它的表达式树看起来像这样:

Programs.Select(d => d.Protocols.ForUser(value(Repository<>c__DisplayClass1_0).userId))

Entity Framework 不知道什么ForUser()应该做的。就它而言,你可以写 ForUser()做一些在 SQL 中不可能做的事情。所以它会告诉您这不是受支持的方法。

关于c# - Linq to 实体扩展方法内部查询 (EF6),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39623788/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com