2012-11-27 11 views
7

Mam zapytanie LINQ, które składa się z anonimowego obiektu.Dynamicznie konstruuj zapytanie "LIKE" w LINQ do SQL

W danym punkcie chcę ograniczyć wyniki przez przychodzące parametry wyszukiwania, ale może to być jeden lub więcej parametrów i chcę wykonać "LIKE X LUB LIKE y LUB LIKE z" używając tych parametrów.

W kodzie, to będzie wyglądać następująco:

reservations = reservations.Where(r => 
    r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) || 
    r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) || 
    // Parameter 3, 4, 5,.. 
); 

Jak mógłbym skonstruować ten dynamicznie, wiedząc, że reservations jest typu IQueryable<'a> (anonymous object)? Rozglądałem się po różnych zasobach i mogę tylko znaleźć sposób, aby to zrobić, gdy znam typ, a nie przy użyciu typów anonimowych.

Ważne jest, aby wiedzieć, że to jest LINQ do SQL, więc powinno być przetłumaczone na zapytaniu SQL i nie może być filtrowane w pamięci ...

Odpowiedz

2

Istnieją dwa możliwe sposoby:

  1. budowania Expression, jak podkreślił Coincoin
  2. Umieszczenie wszystkich parametrów do tablicy i korzystania Any:

    var parameters = new [] { parameter1, parameter2, /*...*/ } 
    reservations = reservations 
        .Where(r => 
         parameters.Any(p => r.GuestFirstName.Contains(p) 
              || r.GuestLastName.Contains(p))); 
    
1

chciałbym napisać własną metodę rozszerzenia Generic:

public static class CollectionHelper 
{ 
    public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values) 
    { 
     var lambda = CombineLambdas<T>(properties, values); 
     var result = typeof (Queryable).GetMethods().First(
      method => method.Name == "Where" 
         && method.IsGenericMethodDefinition) 
             .MakeGenericMethod(typeof (T)) 
             .Invoke(null, new object[] {source, lambda}); 
     return (IQueryable<T>) result; 
    } 

    // combine lambda expressions using OR operator 
    private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values) 
    { 
     var param = Expression.Parameter(typeof (T)); 
     LambdaExpression prev = null; 
     foreach (var value in values) 
     { 
      foreach (var property in properties) 
      { 
       LambdaExpression current = GetContainsExpression<T>(property, value); 
       if (prev != null) 
       { 
        Expression body = Expression.Or(Expression.Invoke(prev, param), 
                Expression.Invoke(current, param)); 
        prev = Expression.Lambda(body, param); 
       } 
       prev = prev ?? current; 
      } 
     } 
     return prev; 
    } 

    // construct expression tree to represent String.Contains 
    private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue) 
    { 
     var parameterExp = Expression.Parameter(typeof (T), "type"); 
     var propertyExp = Expression.Property(parameterExp, propertyName); 
     var method = typeof (string).GetMethod("Contains", new[] {typeof (string)}); 
     var someValue = Expression.Constant(propertyValue, typeof (string)); 
     var containsMethodExp = Expression.Call(propertyExp, method, someValue); 

     return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp); 
    } 
} 

i wykorzystania:

var reservations = new List<TheType>() // sample collection 
    { 
     new TheType {FirstName = "aa", LastName = "bb"}, 
     new TheType {FirstName = "cc", LastName = "dd"}, 
     new TheType {FirstName = "ee", LastName = "ff"} 
    }.AsQueryable(); 

var filtered = reservations 
    .Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"}); 
/* returnes 2 elements: 
* {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */ 

nie wiem ogólne rozwiązanie, które chcesz mieć - jeśli istnieje jakikolwiek , ale mam nadzieję, że jest to akceptowalna alternatywa, która rozwiązuje twój przypadek, dynamicznie budując pożądany filtr.

0

znalazłem rozwiązanie po pewnym debugowania, ale tworzę WhereFilter z wieloma selektorów, jedna na imię i jeden dla LastName ..

Jest to metoda rozszerzenie:

public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors) 
{ 
    List<Expression> expressions = new List<Expression>(); 

    var param = Expression.Parameter(typeof(T), "p"); 

    var bodies = new List<MemberExpression>(); 
    foreach (var s in selectors) 
    { 
     bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name)); 
    } 

    foreach (var v in possibleValues) 
    { 
     foreach(var b in bodies) { 
      expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v))); 
     } 
    } 

    var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal)); 

    return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param)); 
} 

To może być użyte w ten sposób:

reservations = reservations.WhereFilter(
    array_of_allowed_values, 
    r => r.GuestFirstName, 
    r => r.GuestLastName 
); 

Sprawdziłem ciąg śledzenia zapytania i faktycznie przetłumaczyłem go na SQL, więc filtrowanie odbywa się w bazie danych.

+0

To rozwiązanie wymaga podania przez użytkownika wyrażenia lambda do podpisu funkcji. O ile zrozumiałem twoje pytanie, twoim wymaganiem było zbudowanie ich dynamicznie. Jako przykład rozważ, że masz wywoływane działanie w wyniku żądania GET w środowisku WEB. W parametrach zapytania nazwy właściwości do filtrowania są wysyłane na serwer wraz z ich możliwymi wartościami. W twoim przypadku musisz konstruować wyrażenia lambda na podstawie nazw właściwości, które otrzymałeś od klienta, zamiast tylko przekazywać je dalej do funkcji filtrowania. – jwaliszko