将 lambda 表达式移动到 Entity Framework 中的静态扩展方法

Move a lambda expression to a static extension method in Entity Framework

我正在尝试将 Entity Framework 查询的以下行重构为通用静态扩展方法:

dbContext.Employees
         .Where(e => permissionResolver.AuthorizedUsers.Select(p => p.Id).Contains(e.Id))
         .OrderBy(...)

PermissionResolver 只是我从中收到 ID 列表的一个实例,用于与存储在当前记录中的用户 ID 进行比较。它完美地编译成 SQL 语句 WHERE Id IN (....)

现在我正在尝试为 IQueryable<T> 创建一个扩展方法,我可以将其用于任何类型的记录,我只想传入一个 属性,其中存储了所有者的 ID。

这就是我想出的:

public static IQueryable<T> AuthorizedRecords<T>(this IQueryable<T> query, Expression<Func<T, Int32>> property, IPermissionResolver permissionResolver)
{
    Expression<Func<T, Boolean>> idIsAuthorized = entity => permissionResolver.AuthorizedUsers.Select(e => e.Id).ToList().Contains(property.Compile()(entity));                

    return query.Where(idIsAuthorized);
}

我收到一个运行时错误,该表达式无法转换为 SQL。

如何将 属性 表达式组合到可以正确翻译成 SQL 的主查询表达式?有没有更好的方法重写查询表达式?

简短的回答是您不能在 entity framework 查询中使用自定义扩展方法。在引擎盖下 entity framework 将表达式树 Expression<Func<>> 解析为 sql 查询。将任何可能的扩展方法转换为 sql 似乎是不可能的,因此他们支持有限的 Linq 方法集来正确转换它。关于表达式树的一些有用信息:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees.
您可以进行一些重构来简化您的查询,即使用 Any 方法,如下所示:

dbContext.Employees
    .Where(e => permissionResolver.AuthorizedUsers.Any(u => u.Id == e.Id))
    .OrderBy(...

property.Compile() 将表达式树转换为委托,此委托无法正确转换回表达式 tree/SQL.

你需要像这样构建表达式树:

var ids = permissionResolver.AuthorizedUsers.Select(e => e.Id).ToList().AsEnumerable();
// method Enumerable.Contains<int>()
var methodContains = typeof(System.Linq.Enumerable).GetMethods()
    .Where(m => m.Name == "Contains" && m.GetParameters().Length == 2)
    .First()
    .MakeGenericMethod(typeof(int));

 var lambdaParam = property.Parameters.Single();
 var lambda = Expression.Lambda(
     Expression.Call(
         methodContains,
         Expression.Constant(ids), 
         property.Body),
     lambdaParam
 );
 var predicate = (Expression<Func<T, bool>>)lambda;
 return query.Where(predicate);