Перемещение лямбда-выражения в метод расширения stati c в Entity Framework - PullRequest
0 голосов
/ 07 мая 2020

Я пытаюсь преобразовать следующую строку запроса Entity Framework в обобщенный метод расширения stati c:

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

PermissionResolver - это просто экземпляр, я получаю список идентификаторов от до сравнить с идентификатором пользователя, хранящимся в текущей записи. Он отлично компилируется в оператор SQL WHERE Id IN (....).

Теперь я пытаюсь создать метод расширения для IQueryable<T>, который я могу использовать для любого типа записи, я просто хочу передать свойство, в котором хранится идентификатор владельца.

Итак, вот что я придумал:

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? Есть ли лучший способ переписать выражение запроса?

Ответы [ 2 ]

1 голос
/ 07 мая 2020

property.Compile() преобразует дерево выражений в делегат, этот делегат не может быть должным образом переведен обратно в дерево выражений / 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);
0 голосов
/ 07 мая 2020

Короткий ответ: вы не можете использовать собственные методы расширения в запросах структуры сущностей. Под капотом 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(...
...