2009-08-12 4 views
25

На основе my question from yesterday:Как Append к выражению

если бы мне пришлось добавить в существующий «где» выражение, как бы я добавить?

Expression<Func<Client, bool>> clientWhere = c => true; 

if (filterByClientFName) 
{ 
    clientWhere = c => c.ClientFName == searchForClientFName; 
} 

if (filterByClientLName) 
    { 
     clientWhere = c => c.ClientLName == searchForClientLName; 
    } 

Пользователь может ввести либо имя, либо фамилию, или и то, и другое. Если они вводят оба значения, я хочу добавить выражение. Попытка увидеть, если есть эквивалент Append, где я мог бы сделать

clientWhere.Append or clientWhere += add new expression 

или что-то подобное

+0

Я пытаюсь найти подобное решение, потому что мы используем наш старый инструмент ORML, разработанный нашей командой, который поддерживает операции добавления с помощью «AND» или «OR», и наш код сильно зависит от такого расширения. До сих пор мы не могли переключиться на linq, но в основном linq создает IExpression , и если вы найдете способ создать IExpression дерево самостоятельно, это может помочь. –

ответ

7

Это сложный сценарий. Вы почти строите свой собственный механизм запросов поверх LINQ. Решение JaredPar (куда оно пошло?) Отлично, если вы хотите логическое И между всеми вашими критериями, но это может быть не всегда так.

Когда я пререкания с этим в одном из моих проектов в последнее время, я создал два списка:

List<Predicate<T>> andCriteria; 
List<Predicate<T>> orCriteria; 

(В этом случае Т Клиент, для вас)

Я бы заселить Списки с предикатами, которые я хочу быть правдой. Например,

decimal salRequirement = 50000.00; 
andCriteria.Add(c => c.Salary > salRequirement); 
orCriteria.Add(c => c.IsMarried); 

Тогда я бы уточнил все критерии в предложении «Списки в моем приложении». Например:

Expression<Func<Client, bool>> clientWhere = 
    c => andCriteria.All(pred => pred(c)) && orCriteria.Any(pred => pred(c)); 

Это также может быть сделано с использованием цикла for for loop. Не забудьте использовать правильный порядок операций при применении предложений OR и AND.

+0

Обратите внимание, что это также касается случая, когда вы хотите сделать больше, чем просто использовать «==», поскольку предикаты могут быть * любой * булевой функцией. – JoshJordan

+0

Хорошее решение Josh! – grenade

+1

Josh: если я построю его с вашим стилем, который отлично подходит, а затем вызовет запрос: var query = from C in db.clients.Where (clientWhere) присоединиться к O в db.orders.Where (orderWhere) on c.clientid равно O.clientid Соединить P в db.products.Where (productWhere) на O.productid равен P.productid выбрать новый {C, O}; Я получаю эту ошибку: Локальная последовательность не может использоваться в LINQ to SQL реализации операторов запроса, кроме оператора Contains(). – 2009-08-12 15:32:51

33

Я считаю, что вы можете просто сделать следующее:

Expression<Func<Client, bool>> clientWhere = c => true; 

if (filterByClientFName) 
{ 
    var prefix = clientWhere.Compile(); 
    clientWhere = c => prefix(c) && c.ClientFName == searchForClientFName; 
} 
if (filterByClientLName) 
{ 
    var prefix = clientWhere.Compile(); 
    clientWhere = c => prefix(c) && c.ClientLName == searchForClientLName; 
} 

Если вам нужно держать все в Expression -land (для использования с IQueryable), вы также можете сделать следующее:

Expression<Func<Client, bool>> clientWhere = c => true; 

if (filterByClientFName) 
{ 
    Expression<Func<Client, bool>> newPred = 
     c => c.ClientFName == searchForClientFName; 
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
     Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters); 
} 
if (filterByClientLName) 
{ 
    Expression<Func<Client, bool>> newPred = 
     c => c.ClientLName == searchForClientLName; 
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
     Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters); 
} 

Это определение можно сделать более подробным, указав этот метод расширения:

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); 
} 
+5

Джейсон: Изучение всей информации, которую все предоставили. Я пробовал свой путь, определяя расширение в статическом классе, и получил ошибку: Бинарный оператор AndAlso не определен для типов «System.Func'2 [Models.Client, System.Boolean]» и 'System.Func'2 [Models.Client, System.Boolean]. – 2009-08-12 18:36:31

+4

Даже если вы исправите ошибку в методе расширения, это очень хрупкий способ сделать это. Дополнительную информацию см. В моем ответе на http://stackoverflow.com/questions/2231302/append-to-an-expression-c. –

+1

@ Джейсон: Слишком много пользователей с отображаемым именем «Джейсон», на мой взгляд. – jason

6

Посмотрите на Predicate Builder, я считаю, что это может работать для вас.

+1

Predicate Builder делает трюк. Он отлично работает с LinqToSQL и, прежде всего, его функции, прост в использовании. –

+0

Это тоже Нугет, отлично подходит для меня, спасибо! – VinnyG

0

Или что-то добавить к Джошу (положил его в мешок трюков):

public static IQueryable<TSource> ObjectFilter<TSource>(this TSource SearchObject, List<Predicate<TSource>> andCriteria, List<Predicate<TSource>> orCriteria) where TSource : IQueryable<TSource> 
     { 
      //Yeah :) 
      Expression<Func<TSource, bool>> ObjectWhere = O => andCriteria.All(pred => pred(O)) && orCriteria.Any(pred => pred(O)); 
      return SearchObject.Where<TSource>(ObjectWhere); 
     } 
1

подмигнули не совсем ответ на ваш вопрос, но я искал то же самое вы, и тогда я нашел лучший ответ на мой вопрос.

Вместо построения динамического Expression, вы могли бы извлечь IQueryable , а затем фильтровать то, что вы хотите, как это:

var customers = CustomerRepository.AllEntities(); 

if (!forename.IsNullOrEmpty()) 
    customers = customers.Where(p => p.Forename == forename); 
if (!familyname.IsNullOrEmpty()) 
    customers = customers.Where(p => p.FamilyNames.Any(n => n.Name==familyname)); 
if (dob.HasValue) 
    customers = customers.Where(p => p.DOB == dob); 

Примечание: Я был обеспокоен выполнением более одного заявления «.где» потому что я боялся, что это сгенерирует более одного запроса в DataBase или потому, что мне придется извлекать все записи, а затем фильтровать их, но это не так, Linq dynamic генерирует только один запрос только при вызове .ToList ().

Here вы можете увидеть оригинальный вопрос, на который я привел пример.

0

Я попытался реализовать такие вещи. Принял у меня день, чтобы узнать. Мое решение основано на фильтре в цикле на основе массива предиката. В качестве примечания, это полностью общее и основанное отражение, потому что единственная информация о классе и поле - это 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, это база одна (с строкового типа или что-то еще), вы можете улучшить его с более functionnality:

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: это единица работы репозитория, аргумент, который я использую для инициализации класса модели Hashtable FieldsNameToGet: индекс/значение списка поля, которое я хочу вернуть Hashtable FieldFilter: ключ/значение с полем Name/Содержание, используемое для выражения Lambda

Удачи.

Смежные вопросы