Linq: warunkowe dodawanie warunków do klauzuli Where

106

Mam takie zapytanie

(from u in DataContext.Users
       where u.Division == strUserDiv 
       && u.Age > 18
       && u.Height > strHeightinFeet  
       select new DTO_UserMaster
       {
         Prop1 = u.Name,
       }).ToList();

Chcę dodać różne warunki, takie jak wiek, wzrost na podstawie tego, czy te warunki zostały dostarczone do metody uruchamiającej to zapytanie. Wszystkie warunki będą obejmować podział użytkowników. Jeśli podano wiek, chcę to dodać do zapytania. Podobnie, jeśli podano wysokość, chcę to również dodać.

Gdyby to miało być zrobione za pomocą zapytań sql, użyłbym konstruktora ciągów, aby dodać je do głównego zapytania strSQL. Ale tutaj w Linq mogę myśleć tylko o zastosowaniu warunku IF, w którym napiszę to samo zapytanie trzykrotnie, z każdym blokiem IF zawierającym dodatkowy warunek. Czy jest lepszy sposób na zrobienie tego?

user20358
źródło

Odpowiedzi:

186

Jeśli nie wywołasz ToList()i nie utworzysz ostatecznego mapowania na typ DTO, możesz dodawać Whereklauzule w trakcie i budować wyniki na końcu:

var query = from u in DataContext.Users
   where u.Division == strUserDiv 
   && u.Age > 18
   && u.Height > strHeightinFeet
   select u;

if (useAge)
   query = query.Where(u => u.Age > age);

if (useHeight)
   query = query.Where(u => u.Height > strHeightinFeet);

// Build the results at the end
var results = query.Select(u => new DTO_UserMaster
   {
     Prop1 = u.Name,
   }).ToList();

To nadal spowoduje tylko jedno wywołanie bazy danych, które będzie tak samo wydajne, jak napisanie zapytania w jednym przebiegu.

Reed Copsey
źródło
1
Czy muszę umieścić wszystkie warunki gdzie w instrukcji „var query = ..”?
user20358
4
Kolejne warunki gdzie są agregowane jako OR lub AND?
Vi100
4
@ vi100 będą dodatkowymi filtrami, więc ORAZ
Reed Copsey
Dzięki Bogu za prostotę! Mam dość oglądania ponad 20 liniowych zapytań Linq, kiedy powyższe jest o wiele bardziej czytelne
justanotherdev
Dlaczego otrzymuję ten błądLINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
Ali Umair
19

Zwykle używam łączenia metod, ale mam ten sam problem. A oto rozszerzenie, którego używam

public static IQueryable<T> ConditionalWhere<T>(
        this IQueryable<T> source, 
        Func<bool> condition,
        Expression<Func<T, bool>> predicate)
    {
        if (condition())
        {
            return source.Where(predicate);
        }

        return source;
    }

Pomaga uniknąć zerwania łańcucha. Te same ConditionalOrderByi ConditionalOrderByDescendingsą pomocne.

Yuriy Granovskiy
źródło
Pomocne, ale czy mógłbyś dodać przykład tego, jak będzie wyglądać w użyciu.
Suncat2000
1
Powinno wyglądać następująco: var owoce = await db.Fruits .ConditionalWhere (() => color! = Null, f => f.Color == color) .ConditionalWhere (() => ripe! = Null, f => f .Ripe == ripe) .ToListAsync ();
Yuriy Granovskiy
4
Działa świetnie! Dzięki! Zrobiłem również przeciążenie warunku jako prostej wartości logicznej zamiast funkcji, aby uczynić go bardziej intuicyjnym, gdy delegat zwiększyłby niepotrzebną złożoność. Używam tej metody rozszerzenia dość często i bardzo doceniam twoje rozwiązanie.
Suncat2000
18

jedna opcja.

bool? age = null

(from u in DataContext.Users
           where u.Division == strUserDiv 
           && (age == null || (age != null && u.Age > age.Value))
           && u.Height > strHeightinFeet  
           select new DTO_UserMaster
           {
             Prop1 = u.Name,
           }).ToList();

lub możesz przełączyć się na składnię metody dla linq i użyć warunków if, aby dołączyć wyrażenia do klauzuli where.

Matthew Vines
źródło
4

Po prostu używam go w mojej klauzuli Where as

    public IList<ent_para> getList(ent_para para){
     db.table1.Where(w=>(para.abc!=""?w.para==para.abc:true==true) && (para.xyz!=""?w.xyz==para.xyz:true==true)).ToList();
}
Posiadać
źródło
3

Na podstawie określonego warunku dodaj warunek gdzie ...

from u in DataContext.Users
where u.Division == strUserDiv 
&& u.Age != null ? u.Age > 18 : 1== 1
&& u.Height != null ? u.Height > 18 : 1== 1
&& u.Height != null ? u.Height > 18 : 1== 1
 select new DTO_UserMaster
       {
         Prop1 = u.Name,
       }).ToList();
Melu
źródło
2

Oto mój kod, aby zrobić podobną rzecz. Jest to metoda w moim interfejsie API usługi sieci Web WCF SOAP.

    public FruitListResponse GetFruits(string color, bool? ripe)
    {
        try
        {
            FruitContext db = new FruitContext();
            var query = db.Fruits.Select(f => f);
            if (color != null)
            {
                query = query.Where(f => f.Color == color);
            }
            if (ripe != null)
            {
                query = query.Where(f => f.Ripe == ripe);
            }
            return new FruitListResponse
            {
                Result = query.Select(f => new Fruit { Id = f.FruitId, Name = f.Name }).ToList()
            };
        }
        catch (Exception e)
        {
            return new FruitListResponse { ErrorMessage = e.Message };
        }
    }

Podstawowe zapytanie Select(f => f)oznacza w zasadzie WSZYSTKO, a Whereklauzule są do niego opcjonalnie dołączone. Finał Selectjest opcjonalny. Używam do konwersji obiektów wierszy bazy danych na obiekty wynikowe „Owoce”.

John Henckel
źródło
0

Zakładając następujący parametr,

Int? Age = 18;

Po prostu używając &&i ||operatorów warunkowych możemy mieć inną wersję.

(from u in DataContext.Users
where u.Division == strUserDiv 
    && (Age == null || u.Age > Age)
    && (Param1 == null || u.param1 == Param1)
    && u.Height > strHeightinFeet
select new DTO_UserMaster
{
    Prop1 = u.Name,
}).ToList();

Podobnie jak Param1, możesz dodać dowolną liczbę parametrów warunku wyszukiwania.

Sushant Yelpale
źródło
0

Właśnie natknąłem się na to, szukając czegoś innego, ale pomyślałem, że wrzucę wersję lambda.

Najpierw utworzyłbym taką klasę, aby przekazywała parametry do warstwy danych:

   public class SearchParameters() {
       public int? Age {get; set;}
       public string Division {get;set;}
       etc
    }

Następnie w mojej warstwie danych coś takiego:

public IQueryable<User> SearchUsers(SearchParameters params) 
{
    var query = Context.Users;
    if (params.Age.HasValue)
    {
         query = query.Where(u => u.Age == params.Age.Value);
    }
    if (!string.IsNullOrEmpty(params.Division)
    {
        query = query.Where(u => u.Division == params.Division);
    }
    etc
    return query;
}

To, gdzie zmaterializujesz zapytanie, zależy od Ciebie. Może istnieć warstwa między aplikacją a danymi, która konwertuje reprezentacje specyficzne dla db na db-agnostic (być może wykonujesz zapytania z wielu źródeł danych). Ta warstwa może pobierać wiele typów zapytań z tych źródeł i na przykład mapować je do wspólnej reprezentacji POCO.

Scott Peterson
źródło
Ups, nie widziałem odpowiedzi Johna Henckela. Ten sam pomysł.
Scott Peterson
0

Aby dodać do powyższej akceptowanej odpowiedzi tutaj , jeśli wykonujesz dynamiczne wyszukiwanie na złączeniu, rozważ zwrócenie nowego obiektu z obiema tabelami (t1, t2) w początkowym zapytaniu linq, abyś mógł uzyskać do nich dostęp indywidualnie, aby wykonać warunkowe Szukaj.

var query = from t1 in _context.Table1
            join t2 in _context.Table2 on t1.Table1Id equals t2.Table1IdId
            select new { t1, t2 };

        if (!string.IsNullOrEmpty(searchProperty1))
        {
            query = query.Where(collection => collection.t1.TableColumn == searchProperty1);
        }
        if (!string.IsNullOrEmpty(searchProperty2))
        {
            query = query.Where(collection => collection.t2.TableColumn == searchProperty2);
        }
        ....etc.

Otrzymałem odpowiedź, której szukałem tutaj, w odniesieniu do łączenia dwóch tabel i odpytywania określonych kolumn w którejkolwiek z tabel

Riaan Saayman
źródło