gpt4 book ai didi

c# - 为什么在通过泛型方法创建时在本地评估 Select 之后的 Linq "where"表达式?

转载 作者:太空狗 更新时间:2023-10-30 01:12:37 25 4
gpt4 key购买 nike

我正在使用泛型实现规范模式,并尝试将标准动态应用于映射实体的投影简单(未映射)版本。一般来说,它工作正常,但 Linq 会在我添加 Select 并在其后应用 Where 时立即在本地计算表达式。

完全相同的 Linq 表达式会产生正确的 SQL 查询,如果我将它构建为局部变量并传递给相同的 Where

这是简化的相关代码片段:

public interface ISomeable
{
string Some { get; set; }
}

public static Expression<Func<T, bool>> GetCriteria<T>() where T : class, ISomeable
{ return e => (e.Some == "Hello"); }


...

Expression<Func<MySimpleEntity, bool>> someCriteria = e => (e.Some == "Hello");
Expression<Func<MySimpleEntity, bool>> someCriteria2 = GetCriteria<MySimpleEntity>();

var query = db.Entities
.Select(s => new MySimpleEntity { Id = s.Id, Some = s.Some });
// if this Select is removed and MySimpleEntity in both expressions replaced with MyFullEntity,
// the issue disappears

// this succeeds
var filteredQueryResults = query.Where(someCriteria).ToList();

// at this point, someCriteria2 is set to the same e => (e.Some == "Hello");

// this fails: why is it evaluated locally and not in SQL? <-----
filteredQueryResults = query.Where(someCriteria2).ToList();

// results in a warning:

/*
* 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning:
* The LINQ expression 'where (new MySimpleEntity() {Id = [s].Id, Some = [s].Some}.Some == "Hello")'
* could not be translated and will be evaluated locally.'.
*/

如何让它为 someCriteria2 生成正确的 SQL 而不是本地评估?

我怀疑我需要某种类型转换,但不确定在哪里。 someCriteriasomeCriteria2 在调试器中看起来完全一样,所以我不知道为什么 Linq 以不同的方式对待它们。

我创建了一个最小的 .Net Core 控制台应用程序来重现该案例。完整的要点在这里:

https://gist.github.com/progmars/eeec32a533dbd2e1f85e551db1bc53f8

NuGet 依赖项:Microsoft.EntityFrameworkCore.SqlServer"Version="2.2.6"Microsoft.Extensions.Logging"Version="2.2.0"Microsoft.Extensions.Logging.Console"Version="2.2.0"

一些解释:

这与同一个查询被执行两次无关。如果我注释掉第一个 query.Where(someCriteria).ToList() ,第二次使用 someCriteria2 的调用仍然无法生成有效的 SQL。但是,如果我将第二个查询的 someCriteria2 替换为 someCriteria 并让它运行,我会在控制台中得到两个完全有效的 SQL 查询。所以,这都与 someCriteria2Select 投影的泛型有关——出于某种原因,Linq 不会对这两个变量一视同仁,即使编译器(和调试器观察)认为它们是完全相同的类型。

最佳答案

问题类似于The LINQ expression could not be translated for base propertyHow to use inherited properties in EF Core expressions? , 但在这种情况下 MemberInfoDeclaringTypeReflectedType 都指向 ISomeable 接口(interface)而不是实际的类.

同样,这在 Select 场景中以某种方式混淆了 EF Core。我检查了最新的 EF Core 3.0 预览版,它也不起作用。您可以考虑将其发布到他们的问题跟踪器。

目前我能提供的唯一解决方法是使用自定义 ExpressionVisitor 对表达式进行后处理,并将成员访问器绑定(bind)到实际类。像这样:

public static partial class ExpressionUtils
{
public static Expression<T> FixMemberAccess<T>(this Expression<T> source)
{
var body = new MemberAccessFixer().Visit(source.Body);
if (body == source.Body) return source;
return source.Update(body, source.Parameters);
}

class MemberAccessFixer : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != null && node.Expression.Type != node.Member.DeclaringType)
{
var member = node.Expression.Type.GetMember(node.Member.Name).Single();
if (member.ReflectedType != member.DeclaringType)
member = member.DeclaringType.GetMember(member.Name).Single();
return Expression.MakeMemberAccess(node.Expression, member);
}
return base.VisitMember(node);
}
}
}

现在

var someCriteria2 = GetCriteria<MySimpleEntity>().FixMemberAccess();

将生成与工作编译时 someCriteria 表达式完全相同的表达式,并且没有客户端评估。

注意:您仍然需要 class 约束,以避免上一个问题中的转换问题并使此解决方法有效。

关于c# - 为什么在通过泛型方法创建时在本地评估 Select 之后的 Linq "where"表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57389115/

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