为什么在通过泛型方法创建时,Select 后的 Linq“where”表达式会在本地求值?

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

如果我将其构建为局部变量并传递给相同的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.'. 

                 */


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


我怀疑我需要某种类型的选角,但不确定在哪里。两者在调试器中看起来完全相同,所以我不知道为什么 Linq 会以不同的方式对待它们someCriteria。someCriteria2


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


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 查询。因此,这一切都与泛型someCriteria2和Select投影有关 - 由于某种原因,Linq 不会以相同的方式对待这两个变量,即使编译器(和调试器监视)认为它们是相同的确切类型。



慕尼黑8549860
浏览 95回答 2
2回答

慕姐8265434

问题类似于无法转换基本属性的 LINQ 表达式和如何在 EF Core 表达式中使用继承的属性?,但在这种情况下, 和DeclaringType都ReflectedType指向MemberInfo接口ISomeable而不是实际的类。这又在某种程度上让场景中的 EF Core 感到困惑Select。我检查了最新的 EF Core 3.0 预览版,它也不起作用。您可以考虑将其发布到他们的问题跟踪器。到目前为止,我可以提供的唯一解决方法是使用自定义后处理表达式ExpressionVisitor并将成员访问器绑定到实际的类。像这样的东西: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,以避免上一个问题中的转换问题并使此解决方法发挥作用。

慕婉清6462132

我认为你的代码的问题是GetCriteria<MySimpleEntity>();linq无法直接翻译sql或没有直接翻译。如果你想使用它。执行ToList()然后添加.Where(someCriteria2).ToList();.&nbsp;在观察者中,它认为/评估它是相同的。但在查询本身中,生成 sql 似乎并不是这样工作的。我还经历过,在我的DateTime扩展方法中,即使将其转换为string我的扩展Where方法,我也必须在linq查询之外执行它并添加它var dateUtc = DateTime.UtcNow.ExtensionMethod();...Where(x => x.Date >= dateUtc)或者我在我的和/或FirstorDefault, First, ToList()之前先执行selectwhere
打开App,查看更多内容
随时随地看视频慕课网APP