c# - 为什么通过泛型方法创建 Select 后的 Linq“where”表达式在本地求值?
问题描述
我正在使用泛型实现规范模式,并尝试将标准动态应用于映射实体的投影简单(未映射)版本。一般来说,它工作正常,但 Linq 会在我添加Select
并应用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
?
我怀疑我需要某种铸造,但不确定在哪里。两者在调试器someCriteria
中someCriteria2
看起来完全一样,所以我不知道为什么 Linq 以不同的方式对待它们。
我创建了一个最小的 .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 不会将这两个变量视为相同,即使编译器(和调试器监视)认为它们是相同的确切类型。
解决方案
问题类似于无法为基本属性翻译 LINQ 表达式和如何在 EF Core 表达式中使用继承的属性?,但在这种情况下,DeclaringType
和ReflectedType
都MemberInfo
指向ISomeable
接口而不是实际的类。
这再次以某种方式混淆了Select
场景中的 EF Core。我检查了最新的 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
约束以避免先前问题中的强制转换问题并使此解决方法有效。
推荐阅读
- javascript - 我们可以使用 getSelection 和 document.selection 来发现一个单词来删除它吗?
- json - gltf 示例文档拼写错误?
- r - 如何将excel表格读入R中的一个数据框并跳过某些行
- r - ggplot总和与条件
- java - java中的近似平方根
- bash - bash 将块代码输出重定向到函数中
- c++ - 如何存储多个原子变量?
- c - 验证 C 中的字符输入
- google-play-games - Google Play 排行榜集成
- opencv - rows, cols, _ = frame.shape AttributeError: 'tuple' object has no attribute 'shape'