为什么Linq会“在哪里"?通过通用方法创建后,Select之后的表达式会在本地求值吗? [英] Why Linq "where" expression after Select gets evaluated locally when created through a generic method?

查看:62
本文介绍了为什么Linq会“在哪里"?通过通用方法创建后,Select之后的表达式会在本地求值吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用泛型实现规范模式,并试图将条件动态地应用于映射的实体的预计的简单(未映射)版本.通常,它可以正常工作,但是一旦我添加Select并在其后应用Where,Linq就会在本地对表达式求值.

I'm implementing Specification pattern with generics and trying to dynamically apply criteria to projected simple (unmapped) versions of mapped entities. In general, it works fine, but Linq evaluates the expression locally as soon as I add Select and apply Where after it.

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

The exact same Linq expression yields correct SQL query, if I build it as a local variable and pass to the same Where.

这是简化的相关代码段:

Here's the simplified relevant code snippet:

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.'. 
                 */

如何使它生成正确的SQL,而不是为someCriteria2进行本地评估?

How do I make it generate correct SQL instead of local evaluation for someCriteria2?

我怀疑我需要某种类型的演员,但不确定在哪里. someCriteriasomeCriteria2在调试器中看起来完全相同,所以我不知道为什么Linq会区别对待它们.

I suspect I need some kind of casting, but not sure where. Both someCriteria and someCriteria2 look exactly the same in the debugger, so I have no idea why Linq is treating them differently.

我创建了一个最小的.Net Core Console应用程序来重现此案.完整的要旨在这里:

I have created a minimal .Net Core Console app to reproduce the case. The full gist is here:

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"

NuGet dependencies: 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也不会将两个变量视为相同.

It is not related to the fact that the same query is executed twice. If I comment out the first query.Where(someCriteria).ToList() the second call with someCriteria2 still fails to generate valid SQL. However, if I replace someCriteria2 with someCriteria for the second query and let it run, I get two exact valid SQL queries in the console. So, it's all related to generics of someCriteria2 and Select projection - for some reason, Linq doesn't treat both variables the same, even if compiler (and debugger watch) thinks they are the same exact type.

推荐答案

问题类似于如何在EF Core表达式中使用继承的属性?,但在这种情况下,MemberInfo指向ISomeable接口,而不是实际的类.

The problem is similar to The LINQ expression could not be translated for base property and How to use inherited properties in EF Core expressions?, but in this case both the DeclaringType and ReflectedType of the MemberInfo point to ISomeable interface rather than the actual class.

再次以某种方式使Select场景中的EF Core感到困惑.我检查了最新的EF Core 3.0预览版,它也不起作用.您可以考虑将其发布到他们的问题跟踪器.

Again this somehow is confusing EF Core in the Select scenario. I've checked the latest EF Core 3.0 preview and it also doesn't work. You might consider posting it to their issue tracker.

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

The only workaround I could offer so far is to postprocess the expression with custom ExpressionVisitor and bind the member accessors to the actual class. Something like this:

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表达式完全相同的表达式,并且不会进行客户评估.

will produce the exact expression as the working compile time someCriteria expression and no client evaluation.

注意:您仍然需要class约束,以避免先前问题中的强制转换问题并使此变通办法起作用.

Note: You still need the class constraint in order to avoid the casting issue from your previous question and to make this workaround work.

这篇关于为什么Linq会“在哪里"?通过通用方法创建后,Select之后的表达式会在本地求值吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆