У меня есть класс, Пользователи.
У пользователей есть свойство UserId.
У меня есть метод, который выглядит примерно так:
static IQueryable<User> FilterById(this IQueryable<User> p, Func<int, bool> sel)
{
return p.Where(m => sel(m.UserId));
}
Неизбежно, когда я вызываю функцию:
var users = Users.FilterById(m => m > 10);
Я получаю следующее исключение:
Метод 'Система. Возразите DynamicInvoke (Система. Объект [])', не имеет никакого поддерживаемого перевода в SQL.
Там какое-либо решение к этой проблеме? Как далеко вниз кроличья нора Выражения. KillMeAndMyFamily () мне, возможно, придется пойти?
Для разъяснения, почему я делаю это: я использую шаблоны T4 для автоматической генерации простого репозитория и системы каналов. В каналах, вместо записи:
new UserPipe().Where(m => m.UserId > 10 && m.UserName.Contains("oo") && m.LastName == "Wee");
Я хотел бы генерировать что-то как:
new UserPipe()
.UserId(m => m > 10)
.UserName(m => m.Contains("oo"))
.LastName("Wee");
Возьмем для примера UserId
. Вы хотите написать:
new UserPipe().UserId(uid => uid > 10);
и хотите, чтобы это было таким же, как:
new UserPipe().Where(user => user.UserID > 10);
Что вам нужно сделать, так это взять дерево выражений первой версии и преобразовать его во вторую версию.
Итак, сначала измените подпись UserId
, чтобы принимать дерево выражений вместо скомпилированной лямбды:
public static IQueryable<User> UserId(
IQueryable<User> source, Expression<Func<int, bool>> predicate)
Затем напишите метод, который преобразует первое дерево выражения во вторую версию. Давайте посмотрим на два дерева выражений:
Вход:
Lambda uid | BinaryOp > / \ Parameter Constant uid 10
Выход:
Lambda user | BinaryOp > / \ Property Constant UserID 10 | Parameter user
Как видите, все, что вам нужно сделать, это взять тело лямбды и рекурсивно заменить все вхождения параметра uid
со свойством UserId
в параметре user
и создайте новое лямбда-выражение с преобразованным телом и параметром user
.
Для замены можно использовать ExpressionVisitor .
Благодаря dtb, вот что я придумал:
public class ExpressionMemberMerger : ExpressionVisitor
{
MemberExpression mem;
ParameterExpression paramToReplace;
public Expression Visit<TMember, TParamType>(
Expression<Func<TParamType, bool>> exp,
Expression<Func<TMember, TParamType>> mem)
{
//get member expression
this.mem = (MemberExpression)mem.Body;
//get parameter in exp to replace
paramToReplace = exp.Parameters[0];
//replace TParamType with TMember.Param
var newExpressionBody = Visit(exp.Body);
//create lambda
return Expression.Lambda(newExpressionBody, mem.Parameters[0]);
}
protected override Expression VisitParameter(ParameterExpression p)
{
if (p == paramToReplace) return mem;
else return base.VisitParameter(p);
}
}
Теперь я может преобразовать предикат, как мне кажется, с помощью кода, подобного приведенному ниже. Я провел небольшое тестирование этого кода; похоже, он работает, но мне было бы интересно услышать какие-либо комментарии / замечания:
static IQueryable<User> FilterById(this IQueryable<User> p, Expression<Func<int, bool>> sel)
{
var merger = new ExpressionMemberMerger();
Expression<Func<User, int>> mem = m => m.UserId;
var expression = (Expression<Func<User, bool>>)merger.Visit(sel, mem);
return p.Where(expression);
}
На первый взгляд кажется, что вы создаете некоторые выражения, которые Linq не знает, как перевести в T-SQL.
Возможно, я неправильно понимаю, что вы пытаетесь сделать, но если вы хотите создать цепные выражения, понятные Linq To Sql, я настоятельно рекомендую посмотреть на расширения PredicateBuilder здесь . Даже если это не совсем то, что вы хотите, понимание того, как это работает, может дать вам некоторое представление о том, чего вам нужно достичь под прикрытием, чтобы то, что вы делаете, работало.