将 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);
我正在尝试将 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);