조건부 Linq 쿼리


92

우리는 로그 뷰어를 만들고 있습니다. 사용에는 사용자, 심각도 등을 기준으로 필터링 할 수있는 옵션이 있습니다. SQL 날에는 쿼리 문자열에 추가했지만 Linq를 사용하여 수행하고 싶습니다. 조건부로 where 절을 추가하려면 어떻게해야합니까?

답변:


156

특정 기준이 통과 된 경우에만 필터링하려면 다음과 같이하십시오.

var logs = from log in context.Logs
           select log;

if (filterBySeverity)
    logs = logs.Where(p => p.Severity == severity);

if (filterByUser)
    logs = logs.Where(p => p.User == user);

이렇게하면 식 트리가 원하는대로 정확하게 될 수 있습니다. 이렇게하면 생성 된 SQL이 정확히 필요한 것입니다.


2
안녕하세요 AND 대신에 where 절을 OR로 만드는 방법에 대한 제안 사항이 있습니까?
Jon H

1
그래 ... 좀 힘들어. 내가 본 가장 좋은 것은 사양 패턴을 통해 조건자를 사양으로 가져온 다음 사양을 호출하는 것입니다. 또는 (someOtherSpecification). 기본적으로 자신 만의 표현 트리를 작성해야합니다. 여기 예제 코드 및 설명 : codeinsanity.com/archive/2008/08/13/...
대런 코프

어리석은 질문이 있습니다. 이러한 로그가 데이터베이스에서 수집되면 모든 로그를 가져와 메모리에서 필터링합니까? 그렇다면 데이터베이스에 조건을 어떻게 전달할 수
있습니까?

메모리에서 필터링하지 않습니다. 쿼리를 작성하고 데이터베이스의 모든 조건을 전송합니다 (최소한 대부분의 linq-to-x 공급자에 대해)
Darren Kopp

이 오류가LINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
알리 UMAIR

22

목록 / 배열을 기준으로 필터링해야하는 경우 다음을 사용하십시오.

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
    {
        if (Numbers == null)
            Numbers = new List<string>();

        if (Letters == null)
            Letters = new List<string>();

        var q = from d in database.table
                where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                where (Letters.Count == 0 || Letters.Contains(d.Letter))
                select new Data
                {
                    Number = d.Number,
                    Letter = d.Letter,
                };
        return q.ToList();

    }

3
이것이 가장 좋고 가장 정답입니다. 조건부 || 첫 번째 부분 만 비교하고 첫 번째 부분이 사실이면 두 번째 부분은 건너 뜁니다.
Serj Sagan

1
이 구성은 생성 된 SQL 쿼리에있는 표현식의 '또는'부분을 포함합니다. 수락 된 답변은보다 효율적인 진술을 생성합니다. 물론 데이터 공급자의 최적화에 따라 다릅니다. LINQ-to-SQL은 더 나은 최적화를 제공 할 수 있지만 LINQ-to-Entities는 그렇지 않습니다.
Suncat2000 2016

20

Daren과 비슷한 대답을 사용했지만 IQueryable 인터페이스를 사용하여 끝냈습니다.

IQueryable<Log> matches = m_Locator.Logs;

// Users filter
if (usersFilter)
    matches = matches.Where(l => l.UserName == comboBoxUsers.Text);

 // Severity filter
 if (severityFilter)
     matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);

 Logs = (from log in matches
         orderby log.EventTime descending
         select log).ToList();

데이터베이스에 도달하기 전에 쿼리를 작성합니다. 이 명령은 마지막에 .ToList ()까지 실행되지 않습니다.


14

조건부 linq에 관해서는 필터와 파이프 패턴을 매우 좋아합니다.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

기본적으로 IQueryable 및 매개 변수를 사용하는 각 필터 케이스에 대한 확장 메서드를 만듭니다.

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
    return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}

8

유창한 표현 중에 LINQ를 조건부로 활성화 할 수 있도록 확장 메서드로이 문제를 해결했습니다. 이렇게하면 if문으로 식을 분리 할 필요가 없습니다 .

.If() 확장 방법 :

public static IQueryable<TSource> If<TSource>(
        this IQueryable<TSource> source,
        bool condition,
        Func<IQueryable<TSource>, IQueryable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

이를 통해 다음을 수행 할 수 있습니다.

return context.Logs
     .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
     .If(filterByUser, q => q.Where(p => p.User == user))
     .ToList();

다음 IEnumerable<T>은 대부분의 다른 LINQ 식을 처리 하는 버전 도 있습니다 .

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

4

또 다른 옵션은 여기에서 설명하는 PredicateBuilder와 같은 것을 사용하는 것 입니다. 다음과 같은 코드를 작성할 수 있습니다.

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");

var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                  .And (Product.IsSelling());

var query = from p in Data.Products.Where (newKids.Or (classics))
            select p;

Linq 2 SQL에서만 작동합니다. EntityFramework는이 메서드가 작동하는 데 필요한 Expression.Invoke를 구현하지 않습니다. 여기 에이 문제에 대한 질문이 있습니다 .


이는 데이터 전송 개체와 엔터티 모델간에 매핑하기 위해 AutoMapper와 같은 도구와 함께 저장소 위에 비즈니스 논리 계층을 사용하는 사람들에게 훌륭한 방법입니다. 조건 자 작성기를 사용하면 IQueryable을 AutoMapper로 보내기 전에 동적으로 수정할 수 있습니다. 즉, 목록을 메모리로 가져 오기 위해 병합합니다. Entity Framework도 지원합니다.
chrisjsherm

3

이렇게 :

bool lastNameSearch = true/false; // depending if they want to search by last name,

where성명서에 이것을 가지고 :

where (lastNameSearch && name.LastNameSearch == "smith")

즉, 최종 쿼리가 생성 될 때, 경우 lastNameSearch입니다 false쿼리가 완전히 성 검색에 어떤 SQL을 생략합니다.


데이터 공급자에 따라 다릅니다. LINQ-to-Entities는이를 잘 최적화하지 않습니다.
Suncat2000 2016

1

가장 예쁜 것은 아니지만 람다 식을 사용하고 선택적으로 조건을 전달할 수 있습니다. TSQL에서 매개 변수를 선택적으로 만들기 위해 다음을 많이 수행합니다.

WHERE 필드 = @FieldVar 또는 @FieldVar가 NULL입니다.

다음 람다 (인증 확인 예)를 사용하여 동일한 스타일을 복제 할 수 있습니다.

MyDataContext db = new MyDataContext ();

void RunQuery (string param1, string param2, int? param3) {

Func checkUser = 사용자 =>

((param1.Length> 0)? user.Param1 == param1 : 1 == 1) &&

((param2.Length> 0)? user.Param2 == param2 : 1 == 1) &&

((param3! = null)? user.Param3 == param3 : 1 == 1);

사용자 foundUser = db.Users.SingleOrDefault (checkUser);

}


1

최근에 비슷한 요구 사항이 있었고 결국 MSDN에서 이것을 발견했습니다. Visual Studio 2008 용 CSharp 샘플

다운로드의 DynamicQuery 샘플에 포함 된 클래스를 사용하면 런타임에 다음 형식으로 동적 쿼리를 만들 수 있습니다.

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

이를 사용하여 런타임에 쿼리 문자열을 동적으로 빌드하고 Where () 메서드에 전달할 수 있습니다.

string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
var q = from c in db.Customers.Where(queryString, null)
        orderby c.CompanyName
        select c;

1

이 확장 방법을 만들고 사용할 수 있습니다.

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}

0

C #의 && 연산자를 사용하십시오.

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

편집 : 아, 더 자세히 읽어야합니다. 조건부로 추가 절을 추가 하는 방법을 알고 싶었습니다 . 그 경우에는 모르겠습니다. :) 아마도 내가 할 일은 몇 가지 쿼리를 준비하고 내가 필요한 것에 따라 올바른 쿼리를 실행하는 것입니다.


0

외부 방법을 사용할 수 있습니다.

var results =
    from rec in GetSomeRecs()
    where ConditionalCheck(rec)
    select rec;

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

이것은 작동하지만 표현식 트리로 나눌 수는 없습니다. 즉, Linq to SQL은 모든 레코드에 대해 검사 코드를 실행합니다.

또는 :

var results =
    from rec in GetSomeRecs()
    where 
        (!filterBySeverity || rec.Severity == severity) &&
        (!filterByUser|| rec.User == user)
    select rec;

이는 표현식 트리에서 작동 할 수 있습니다. 즉, Linq to SQL이 최적화됩니다.


0

글쎄, 내가 생각한 것은 필터 조건을 일반적인 술어 목록에 넣을 수 있다는 것입니다.

    var list = new List<string> { "me", "you", "meyou", "mow" };

    var predicates = new List<Predicate<string>>();

    predicates.Add(i => i.Contains("me"));
    predicates.Add(i => i.EndsWith("w"));

    var results = new List<string>();

    foreach (var p in predicates)
        results.AddRange(from i in list where p.Invoke(i) select i);               

그러면 "me", "meyou"및 "mow"가 포함 된 목록이 생성됩니다.

모든 술어를 OR하는 완전히 다른 함수에서 술어로 foreach를 수행하여이를 최적화 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.