Linq to Entities를 사용하는 'Contains ()'해결 방법?


86

Silverlight ADO.Net 데이터 서비스 클라이언트 API (및 따라서 Linq To Entities)를 사용하여 where 절의 ID 목록을 사용하는 쿼리를 만들려고합니다. 누구든지 지원되지 않는 포함에 대한 해결 방법을 알고 있습니까?

다음과 같이하고 싶습니다.

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        where txnIds.Contains(t.OpenTransactionId)
        select t;

이것을 시도 :

var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;

그러나 " 'Any'방법은 지원되지 않습니다."


35
참고 : Entity Framework 4 (.NET 4의 경우)에는 "Contains"메서드가 있습니다. 이는 누군가가 그것에 대해 알지 못하는이 문서를 읽는 경우를 대비해 제공됩니다. OP가 EF1 (.NET 3.5)을 사용하고 있다는 것을 알고 있습니다.
DarrellNorton 2010

7
@Darrell 나는 귀하의 의견을 건너 뛰기 때문에 30 분을 낭비했습니다. 나는 당신의 코멘트를 화면에 깜빡이고 윤곽을 표시 할 수 있기를 바랍니다.
Chris Dwyer 2011 년

답변:


97

업데이트 : EF ≥ 4는 Contains직접 (체크 아웃 Any)을 지원 하므로 해결 방법이 필요하지 않습니다.

public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    IEnumerable<TValue> collection
  )
{
  if (selector == null) throw new ArgumentNullException("selector");
  if (collection == null) throw new ArgumentNullException("collection");
  if (!collection.Any()) 
    return query.Where(t => false);

  ParameterExpression p = selector.Parameters.Single();

  IEnumerable<Expression> equals = collection.Select(value =>
     (Expression)Expression.Equal(selector.Body,
          Expression.Constant(value, typeof(TValue))));

  Expression body = equals.Aggregate((accumulate, equal) =>
      Expression.Or(accumulate, equal));

  return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}

//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    params TValue[] collection
  )
{
  return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}

용법:

public static void Main()
{
  using (MyObjectContext context = new MyObjectContext())
  {
    //Using method 1 - collection provided as collection
    var contacts1 =
      context.Contacts.WhereIn(c => c.Name, GetContactNames());

    //Using method 2 - collection provided statically
    var contacts2 = context.Contacts.WhereIn(c => c.Name,
      "Contact1",
      "Contact2",
      "Contact3",
      "Contact4"
      );
  }
}

6
경고; arg가 큰 컬렉션 일 때 (내가 8500 항목 int 목록이었습니다) 스택 오버플로. 그러한 목록을 통과하는 것이 미쳤다고 생각할 수도 있지만, 그럼에도 불구하고이 접근 방식에 결함이 있다고 생각합니다.
dudeNumber4 2010-08-23

2
내가 틀렸다면 정정하십시오. 그러나 이것은 전달 된 컬렉션 (필터)이 빈 집합 일 때 기본적으로 모든 데이터가 발생하여 쿼리 매개 변수를 반환한다는 것을 의미합니다. 모든 값을 필터링 할 것으로 예상했는데 이렇게 할 수있는 방법이 있습니까?
Nap

1
확인 컬렉션이 비어있을 때 결과가 반환되지 않아야 함을 의미하는 경우 위 코드 조각에서 if (!collection.Any()) //action;-replace 작업을 최상의 성능을 위해 요청 된 유형의 빈 쿼리를 반환하는 것으로 대체하거나이 줄을 제거하십시오.
Shimmy Weitzhandler

1
return WhereIn (쿼리, 선택기, 컬렉션); return WhereIn (query, selector, (IEnumerable <TValue>) collection); 원치 않는 재귀를 방지합니다.
Antoine Aubry 2011 년

1
코드에 버그가 있다고 생각합니다. 제공된 값 목록이 비어있는 경우 올바른 동작은 결과를 반환하지 않는 것입니다. 즉, 쿼리의 개체가 컬렉션에 없습니다. 그러나 코드는 정반대를 수행합니다. 모든 값이 반환되지 않고 반환되지 않습니다. 나는 "경우 (! collection.Any은 ()) query.Where (예 => 거짓)을 반환"당신이 원하는 생각
ShadowChaser

18

일부 e-sql을 직접 코딩 할 수 있습니다 (키워드 "it"참고).

return CurrentDataSource.Product.Where("it.ID IN {4,5,6}"); 

다음은 YMMV 컬렉션에서 e-sql을 생성하는 데 사용한 코드입니다.

string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");

1
"it"에 대한 더 많은 정보가 있습니까? "it"접두사는 MSDN 샘플에 표시되지만 "it"이 필요한시기 / 이유에 대한 설명은 어디에서도 찾을 수 없습니다.
Robert Claypool

1
Entity Framework 동적 쿼리에 사용되며 geekswithblogs.net/thanigai/archive/2009/04/29/… 을 살펴보면 Thanigainathan Siranjeevi가 설명합니다.
Shimmy Weitzhandler

13

에서 MSDN :

static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
    Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
    if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
    if (null == values) { throw new ArgumentNullException("values"); }
    ParameterExpression p = valueSelector.Parameters.Single();

    // p => valueSelector(p) == values[0] || valueSelector(p) == ...
    if (!values.Any())
    {
        return e => false;
    }

    var equals = values.Select(
             value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));

    var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(body, p);
} 

쿼리는 다음과 같습니다.

var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));

3
'포함하지 않음'을 수행하려면 BuildContainsExpression 메서드에서 다음과 같이 편집하십시오.-Expression.Equal이 Expression.NotEqual이됩니다.-Expression.Or가 Expression.And가됩니다.
Merritt

2

Silverligth에 대해 잘 모르겠지만 linq to objects에서는 항상 이러한 쿼리에 any ()를 사용합니다.

var q = from t in svc.OpenTranaction
        where txnIds.Any(t.OpenTransactionId)
        select t;

5
Any는 시퀀스 유형의 객체를 사용하지 않습니다. 매개 변수가 없거나 (이 경우 "이것이 비어 있거나 없습니다") 술어를 사용합니다.
Jon Skeet

나는이 대답을 발견하게되어 매우 기쁩니다 :) +1 감사 AndreasN
SDReyes

1

기록을 완료하기 위해 마지막으로 사용한 코드는 다음과 같습니다 (명확성을 위해 오류 검사 생략).

// How the function is called
var q = (from t in svc.OpenTransaction.Expand("Currency,LineItem")
         select t)
         .Where(BuildContainsExpression<OpenTransaction, long>(tt => tt.OpenTransactionId, txnIds));



 // The function to build the contains expression
   static System.Linq.Expressions.Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
                System.Linq.Expressions.Expression<Func<TElement, TValue>> valueSelector, 
                IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            System.Linq.Expressions.ParameterExpression p = valueSelector.Parameters.Single();

            // p => valueSelector(p) == values[0] || valueSelector(p) == ...
            if (!values.Any())
            {
                return e => false;
            }

            var equals = values.Select(value => (System.Linq.Expressions.Expression)System.Linq.Expressions.Expression.Equal(valueSelector.Body, System.Linq.Expressions.Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<System.Linq.Expressions.Expression>((accumulate, equal) => System.Linq.Expressions.Expression.Or(accumulate, equal));
            return System.Linq.Expressions.Expression.Lambda<Func<TElement, bool>>(body, p);
        }


0

매우 감사합니다. WhereIn 확장 방법으로 충분했습니다. 나는 그것을 프로파일 링하고 e-sql과 동일한 SQL 명령을 데이터베이스에 생성했습니다.

public Estado[] GetSomeOtherMore(int[] values)
{
    var result = _context.Estados.WhereIn(args => args.Id, values) ;
    return result.ToArray();
}

생성 :

SELECT 
[Extent1].[intIdFRLEstado] AS [intIdFRLEstado], 
[Extent1].[varDescripcion] AS [varDescripcion]
FROM [dbo].[PVN_FRLEstados] AS [Extent1]
WHERE (2 = [Extent1].[intIdFRLEstado]) OR (4 = [Extent1].[intIdFRLEstado]) OR (8 = [Extent1].[intIdFRLEstado])


0

새로운 사용자 죄송합니다. 실제 답변에 대해 댓글을 달았을 것입니다.하지만 아직 할 수없는 것 같습니다.

어쨌든 BuildContainsExpression ()의 샘플 코드에 대한 답변과 관련하여 데이터베이스 엔터티 (즉, 메모리 내 개체가 아님)에서 해당 메서드를 사용하고 IQueryable을 사용하는 경우 실제로 데이터베이스로 이동해야합니다. 기본적으로 "where in"절을 확인하기 위해 많은 SQL "또는"조건을 수행하기 때문입니다 (확인하려면 SQL 프로필러로 실행).

즉, 여러 BuildContainsExpression ()을 사용하여 IQueryable을 구체화하는 경우 예상대로 끝에 실행되는 하나의 SQL 문으로 변환되지 않습니다.

해결 방법은 여러 LINQ 조인을 사용하여 하나의 SQL 호출로 유지하는 것이 었습니다.


0

선택한 답변 외에.

Nhibernate와 함께 사용하려면로 교체 Expression.Or하고 예외를 Expression.OrElse수정하십시오 Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'.

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