Добавить в выражение
Я следил за этой темой: текст ссылки
Джейсон приводит пример:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}
и его использование как таковое:
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}
У меня есть таблица заказов, и я следовал приведенному выше примеру, меняя имена столбцов, и получаю аналогичную ошибку, которую создатель сообщения имел
Двоичный оператор AndAlso не определен для типов "System.Func 2[Models.Order,System.Boolean]' and 'System.Func
2 [Models.Order, System.Boolean]".
У кого-нибудь есть мысли о том, чего я не хватает?
ОБНОВЛЕНО:
Эрик, я далее следил за тем, что спрашивал пользователь предыдущего сообщения, здесь текст ссылки
У пользователя есть этот
Expression<Func<Client, bool>> clientWhere = c => true;
Expression<Func<Order, bool>> orderWhere = o => true;
Expression<Func<Product, bool>> productWhere = p => true;
if (filterByClient)
{
clientWhere = c => c.ClientID == searchForClientID;
}
Теперь, если у него будут различные условия в filterByClient
, скажем, у него либо есть clientid
, и/или какое-то другое имя столбца, как бы построить выражение clientWhere
?
Ответы
Ответ 1
Вы пытаетесь построить дерево выражений, которое представляет это:
c => true && c.ClientFName == searchForClientFName
Фактически вы создаете дерево выражений, которое представляет это:
c => c=> true && c => c.ClientFName == searchForClientFName
что не имеет никакого смысла.
Теперь вы можете наивно подумать, что это сработает:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
// NOTICE: Combining BODIES:
return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters);
}
Это создаст в вашем случае что-то, представляющее
c => true && c.ClientFName == searchForClientFName
Что выглядит правильно. Но на самом деле это хрупкое. Предположим, вы имели
... d => d.City == "London" ...
... c => c.ClientName == "Fred Smith" ...
и вы использовали этот метод для их объединения. Вы получите объект, представляющий
c => d.City == "London" && c.ClientName == "Fred Smith"
Что, черт возьми, делает там?
Кроме того, параметры сопоставляются идентификатором объекта, а не именем параметра. Если вы сделаете это
... c => c.City == "London" ...
... c => c.ClientName == "Fred Smith" ...
и объединить их в
c => c.City == "London" && c.ClientName == "Fred Smith"
ты в одной лодке; "c" в "c.City" отличается от c, чем два других.
То, что вам действительно нужно сделать, это сделать третий объект параметра, подставить его в телах lambdas для каждого появления их параметров, а затем создать новое дерево выражений лямбда из полученных замещенных тел.
Вы можете создать механизм подстановки, написав посетителя, который проходит над телом дерева выражений, переписывая его, когда он идет.
Ответ 2
Мне было трудно понять hvd answer, поэтому я создал некоторый код, чтобы объяснить его по-другому. hvd должен получить кредит за предложение ExpressionVisitor. Я просто не мог понять этот пример в контексте функций ввода Linq to X, которые я использовал.
Я надеюсь, что это поможет кому-то еще прийти к вопросу с этой точки зрения.
Кроме того, я создал комбинирующий код в качестве методов расширения, чтобы сделать его немного проще в использовании.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy");
Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" }));
Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" }));
Console.ReadLine();
}
public class FullName
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2)
{
return func1.CombineWithAndAlso(func2).Compile();
}
}
public static class CombineExpressions
{
public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
{
return Expression.Lambda<Func<TInput, bool>>(
Expression.AndAlso(
func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
func1.Parameters);
}
public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
{
return Expression.Lambda<Func<TInput, bool>>(
Expression.AndAlso(
func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
func1.Parameters);
}
private class ExpressionParameterReplacer : ExpressionVisitor
{
public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
{
ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
ParameterReplacements.Add(fromParameters[i], toParameters[i]);
}
private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }
protected override Expression VisitParameter(ParameterExpression node)
{
ParameterExpression replacement;
if (ParameterReplacements.TryGetValue(node, out replacement))
node = replacement;
return base.VisitParameter(node);
}
}
}
}
Ответ 3
Если вам это нужно, я создал небольшую свободную библиотеку для создания лямбда-функций на лету без прямого взаимодействия с System.Linq.Expressions. И он может легко справиться с такой ситуацией. Просто чтобы привести пример:
static void Main(string[] args)
{
var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName);
var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName);
Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy");
var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"};
Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck));
toCheck = new FullName {FirstName = "Cat", LastName = "Boy"};
Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck));
Console.ReadLine();
}
Методы GetComparer ищут свойство, переданное как выражение, и находят ho, чтобы получить его значение, затем он создает новое выражение, которое будет обрабатывать comparaison.
В конце вычисляются две функции, вызывающие "комбинированную" функцию.
Если вам нужно больше проверок, вы можете использовать массив и перебирать его внутри "комбинированной лямбды"
Код и документация для библиотеки находятся здесь: Kendar Expression Builder
Хотя пакет nuget находится здесь: Nuget Expression Builder
Ответ 4
Я пытался реализовать такие вещи. Принял у меня день, чтобы узнать.
Мое решение основано на фильтрах в цикле на основе массива предикатов.
В качестве примечания, это полностью общее и основанное отражение, потому что единственная информация о классе и поле - это String.
Чтобы сделать это простым, я вызываю непосредственно класс Model, но в проекте вы должны пойти с помощью контроллера, который вызывает модель.
Итак, идем:
Модельная часть, где T является общим в классе
public class DALXmlRepository<T> where T : class
{
public T GetItem(Array predicate)
{
IQueryable<T> QueryList = null;
QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
for (int i = 1; i < predicate.GetLength(0); i++)
{
QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
}
if (QueryList.FirstOrDefault() == null)
throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
return QueryList.FirstOrDefault();
}
}
Теперь LambdaExpression Builder, он базовый (со строковым типом или чем-то еще), вы можете улучшить его с большей функциональностью:
private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
{
LambdaExpression lambda = null;
Expression Criteria = null;
Random r = new Random();
ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());
if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
//Type du champ recherché
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(FieldValue, propType);
Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
}
else
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);
Criteria = Expression.Equal(left, right);
}
lambda = Expression.Lambda(Criteria, predParam);
return lambda;
}
Теперь функция вызова:
public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
{
//Get the type
Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
//Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });
//Building the string type Expression<func<T,bool>> to init the array
Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);
MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });
if (method == null)
throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);
int j = 0;
IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
criterias.Reset();
while (criterias.MoveNext())
{
if (!String.IsNullOrEmpty(criterias.Key.ToString()))
{
lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
}
else
{
throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
}
j++;
}
Object item = method.Invoke(DalInstance, new object[] { lambda });
}
Аргумент:
String Entity: имя класса сущности.
XMLContext: это единица работы репозитория, аргумент, который я использую для инициализации класса Model
Hashtable FieldsNameToGet: индекс/значение списка поля, которое я хочу вернуть
Hashtable FieldFilter: ключ/значение с полем Name/Content, используемым для выражения Lambda
Удачи.