C# 重用排序逻辑
我有一个枚举,描述了一篇文章的某种排序顺序:C# 重用排序逻辑,c#,linq,entity-framework-core,npgsql,.net-5,C#,Linq,Entity Framework Core,Npgsql,.net 5,我有一个枚举,描述了一篇文章的某种排序顺序: 枚举后序 { TitleAsc, 标题ESC, ScoreAsc, ScoreDesc, } 以及重用排序逻辑的扩展方法: 静态类IQueryableExtensions { 公共静态IOrderedQueryable OrderByCommon(此IQueryable可查询,PostOrderBy) =>orderBy开关 { PostOrder.TitleAsc=>queryable.OrderBy(x=>x.Title), PostOrder
枚举后序
{
TitleAsc,
标题ESC,
ScoreAsc,
ScoreDesc,
}
以及重用排序逻辑的扩展方法:
静态类IQueryableExtensions
{
公共静态IOrderedQueryable OrderByCommon(此IQueryable可查询,PostOrderBy)
=>orderBy开关
{
PostOrder.TitleAsc=>queryable.OrderBy(x=>x.Title),
PostOrder.TitleDesc=>queryable.OrderByDescending(x=>x.Title),
PostOrder.ScoreAsc=>queryable.OrderBy(x=>x.Score)。然后是By(x=>x.Title),
PostOrder.ScoreDesc=>queryable.OrderByDescending(x=>x.Score)。然后是by(x=>x.Title),
_=>抛出新的NotSupportedException(),
};
}
扩展方法在正常上下文中使用时有效,但在此处失败:
var输入=PostOrder.ScoreDesc;
var dbContext=newQuestionContext();
var users=dbContext.users
.选择(x=>new
{
用户=x,
Top3Posts=x.Posts.AsQueryable()
.OrderByCommon(输入)
.采取(3)
托利斯先生()
}).ToList();
出现此错误时:
The LINQ expression 'MaterializeCollectionNavigation(
Navigation: User.Posts,
subquery: NavigationExpansionExpression
Source: DbSet<Post>()
.Where(p => EF.Property<Nullable<int>>(u, "Id") != null && object.Equals(
objA: (object)EF.Property<Nullable<int>>(u, "Id"),
objB: (object)EF.Property<Nullable<int>>(p, "AuthorId")))
PendingSelector: p => NavigationTreeExpression
Value: EntityReference: Post
Expression: p
.Where(i => EF.Property<Nullable<int>>(NavigationTreeExpression
Value: EntityReference: User
Expression: u, "Id") != null && object.Equals(
objA: (object)EF.Property<Nullable<int>>(NavigationTreeExpression
Value: EntityReference: User
Expression: u, "Id"),
objB: (object)EF.Property<Nullable<int>>(i, "AuthorId")))
.AsQueryable()
.OrderByCommon(__input_0)
.Take(3)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
LINQ表达式“MaterializeCollectionNavigation”(
导航:User.Posts,
子查询:NavigationExpression
资料来源:DbSet()
其中(p=>EF.Property(u,“Id”)!=null&&object.Equals(
objA:(object)EF.Property(u,“Id”),
objB:(object)EF.Property(p,“AuthorId”))
PendingSelector:p=>NavigationTreeExpression
Value:EntityReference:Post
表达式:p
.Where(i=>EF.Property(NavigationTreeExpression
值:EntityReference:用户
表达式:u,“Id”)!=null&&object.Equals(
objA:(对象)EF.Property(NavigationTreeeExpression
值:EntityReference:用户
表达式:u,“Id”),
objB:(object)EF.Property(i,“AuthorId”))
.AsQueryable()
.OrderByCommon(\u\u输入\u 0)
.Take(3)无法翻译。请以可以翻译的形式重写查询,或通过插入对AsEnumerable()、AsAsAsAsyncEnumerable()、ToList()或ToListSync()的调用显式切换到客户端计算。请参阅https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。
可能是因为它正在表达式中使用
我怎样才能在那里工作
可以找到一个可复制的项目。这是众所周知的问题,没有通用的解决方案
表达式树翻译的一般问题是,它完全基于知识-实际上没有调用任何方法,已知的方法通过签名标识并根据其已知语义进行翻译。这就是为什么不能翻译自定义方法/属性/委托
这个问题通常可以通过使用一些表达式操纵库来解决。在我使用EF6/EF Core的过程中,我尝试了很多方法-LinqKit、NeinLinq、AutoMapper、最近的DelegateDecompiler。所有这些方法都允许用相应的原始表达式替换(扩展)表达式树的部分,就像手动编写它们一样
这种特殊情况下的问题更复杂,因为为了进行翻译,必须实际调用自定义方法。但是如何调用呢?特别是,IQueryble
参数是什么?请注意
x.Posts.AsQueryable()
您没有x
实例,因此没有Posts
集合实例来调用AsQueryable()
并将其传递给自定义方法
一种可能的解决方案是调用将伪LINQ传递给对象IQueryable
,然后在结果查询表达式树中查找并用实际表达式替换它的方法
以下是上述理念的实施情况:
partial class IQueryableExtensions
{
public static IQueryable<T> Transform<T>(this IQueryable<T> source)
{
var expression = new QueryableMethodTransformer().Visit(source.Expression);
return expression == source.Expression ? source : source.Provider.CreateQuery<T>(expression);
}
class QueryableMethodTransformer : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(IQueryableExtensions) &&
node.Method.IsStatic &&
typeof(IQueryable).IsAssignableFrom(node.Method.ReturnType) &&
node.Arguments.Count > 1 &&
node.Arguments[0].Type.IsGenericType &&
node.Arguments[0].Type.GetGenericTypeDefinition() == typeof(IQueryable<>))
{
// Extract arguments
var args = new object[node.Arguments.Count];
int index = 1;
while (index < args.Length && TryExtractValue(Visit(node.Arguments[index]), out args[index]))
index++;
if (index == args.Length)
{
var source = node.Arguments[0];
var elementType = source.Type.GetGenericArguments()[0];
// Create fake queryable instance
var fakeSource = args[0] = EmptyQueryableMethod
.MakeGenericMethod(elementType)
.Invoke(null, null);
// Invoke the method with it
var result = (IQueryable)node.Method.Invoke(null, args);
// Replace it with the actual queryable expression
return new ConstValueReplacer
{
From = fakeSource,
To = source
}.Visit(result.Expression);
}
}
return base.VisitMethodCall(node);
}
static IQueryable<T> EmptyQueryable<T>() => Enumerable.Empty<T>().AsQueryable();
static readonly MethodInfo EmptyQueryableMethod = typeof(QueryableMethodTransformer)
.GetMethod(nameof(EmptyQueryable), BindingFlags.NonPublic | BindingFlags.Static);
static bool TryExtractValue(Expression source, out object value)
{
if (source is ConstantExpression constExpr)
{
value = constExpr.Value;
return true;
}
if (source is MemberExpression memberExpr && TryExtractValue(memberExpr.Expression, out var instance))
{
value = memberExpr.Member is FieldInfo field ? field.GetValue(instance) :
((PropertyInfo)memberExpr.Member).GetValue(instance);
return true;
}
value = null;
return source == null;
}
}
class ConstValueReplacer : ExpressionVisitor
{
public object From;
public Expression To;
protected override Expression VisitConstant(ConstantExpression node) =>
node.Value == From ? To : base.VisitConstant(node);
}
}
现在,可以通过将QueryableMethodTransformer
插入EF Core查询转换管道来避免Transform
调用,但仅调用一个方法就需要大量管道代码。请注意,它必须插入查询预Translator,因为imethodCallTransformer
无法处理IQueryable
(以及通常的IEnumerable
)参数。如果您感兴趣,我的答案将显示如何将DelegateDecompiler插入EF Core,相同的代码可以直接用于插入其他代码(包括此处提供的代码)自定义表达式基于访问者的预处理器。您能详细说明一下错误吗?如果您更改了OrderBy
扩展方法,使其不做任何事情,只返回输入queryable,您会得到相同的错误吗?如果它只返回queryable.Where(x=>x.Title)
?如果它们都有效,那么这就是你对枚举所做的事情。你能编辑问题并向我们展示你在扩展方法中所做的相关部分吗?我在移动ATM上,稍后我会用代码编辑问题,但Entity.Entities属性只是一个Entity列表。它是一个延迟加载的导航属性。基本我认为,唯一的问题是,除非您告诉EF Core如何翻译自定义扩展函数,否则它不知道如何翻译。这在表达式上下文之外并不重要,但在表达式上下文中很重要,其中的所有内容都必须可翻译为SQL。@Shoe这意味着代码的重要部分是issing-无论…
和后面是什么,这里的东西OrderBy(x=>x.Title)
都可以毫无问题地进行翻译,因此您的代码肯定做得更多complex@PanagiotisKanavos我改进了这个问题,并添加了一个可复制的项目。
var users = dbContext.Users
.Select(x => new
{
User = x,
Top3Posts = x.Posts.AsQueryable()
.OrderByCommon(input)
.Take(3)
.ToList()
})
.Transform() // <--
.ToList();