Monday, February 28, 2011

EF3.5에서 “in” 쿼리문 표현을 위한 Contains 메서드 버그(?) 대응법

출처 : http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx

LINQ로 “in” sql 구문을 사용하려면 Contains 확장 메서드를 사용하면 된다.

sql : select * from People where Firstname in {‘Alex’, ‘Colin’, ‘Danny’, ‘Diego’}

linq : var matches = from person in people 
                            where names.Contains(person.Firstname)
                            select person;

그러나 Entity Framework 3.5에서 이 linq 구문은 Conains 메서드에서 다음과 같은 예외를 뱉어낸다.

LINQ to Entities에서 ‘Boolean Contains[Int32](System.Collections.Generic.IEnumerable`1[System.Int32], Int32)’ 메서드를 인식하지 않으므로 이 메서드는 저장소 식으로 변환될 수 없습니다.

이 문제는 EF4.0에서 발생하지 않는다. EF3.5에서도 실제 DB에 질의를 할 경우에 발생한다.

EF3.5에서 이 문제를 해결하기 위해서는 다음과 같은 표현식으로 작성하는 수 밖에 없다고 한다.

var matches = from person in people where 
    person.Firstname == “Alex” ||
    person.Firstname == “Colin” ||
    person.Firstname == “Danny” ||
    person.Firstname == “Diego”
select person;

이 코드를 매번 작성하려면 어지간히 골치아픈일이 아닐 수 없다. 그래서 이러한 표현식을 만들어주는 메서드를 제시하고 있다.

public static Expression<Func<TElement, bool» BuildOrExpression<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();

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