2013-01-31 2 views
2

아래에 전체 테스트 앱을 붙여 넣었습니다. 그것은 상당히 작기 때문에 나는 그것이 문제가되지 않기를 바라고 있습니다. 단순히 콘솔 앱에 잘라 붙여 넣기 만하면됩니다.표현식 트리를 사용하여 Nullable 유형을 필터링하려고 시도합니다.

Person 개체의 속성 중 하나 이상을 필터링 할 수 있어야하고 런타임까지 어떤 개체인지 알 수 없습니다. 나는이 곳 곳곳에서 논의 된 것을 알고 있으며 PredicateBuilder & Dynamic Linq Library과 같은 도구를 사용하고 있습니다. 그러나 토론은 그것들을 정렬과 정렬에 더 집중하는 경향이 있으며, 각각은 그들 자신의 문제로 어려움을 겪고 있습니다. Nullable 유형에 직면했을 때 그래서 나는 이러한 특정 시나리오를 해결할 수있는 보완 필터를 적어도 만들려고한다고 생각했습니다.

아래 예에서는 특정 날짜 이후에 태어난 가족을 걸러 내려고합니다. 이 킥은 필터링되는 개체의 DateOfBirth 필드가 DateTime 속성이라는 것입니다.

내가 얻고 최신 오류가 없음 강제 연산자는 유형 '선택 System.String'와 'System.Nullable`1 [System.DateTime]'사이에 정의되지 않은

입니다.

이는 어느 것이 문제입니다. 저는 여러 가지 다른 방법으로 주조와 변형을 시도했지만 실패의 정도는 다양합니다. 궁극적으로 이것은 DateTime.Parse (-)와 같은 변환 메소드를 저지른 EF 데이터베이스에 적용됩니다.

어시스턴트를 크게 높이세요!

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      List<Person> people = new List<Person>(); 
     people.Add(new Person { FirstName = "Bob", LastName = "Smith", DateOfBirth = DateTime.Parse("1969/01/21"), Weight=207 }); 
     people.Add(new Person { FirstName = "Lisa", LastName = "Smith", DateOfBirth = DateTime.Parse("1974/05/09") }); 
     people.Add(new Person { FirstName = "Jane", LastName = "Smith", DateOfBirth = DateTime.Parse("1999/05/09") }); 
     people.Add(new Person { FirstName = "Lori", LastName = "Jones", DateOfBirth = DateTime.Parse("2002/10/21") }); 
     people.Add(new Person { FirstName = "Patty", LastName = "Smith", DateOfBirth = DateTime.Parse("2012/03/11") }); 
     people.Add(new Person { FirstName = "George", LastName = "Smith", DateOfBirth = DateTime.Parse("2013/06/18"), Weight=6 }); 

      String filterField = "DateOfBirth"; 
      String filterOper = "<="; 
      String filterValue = "2000/01/01"; 

      var oldFamily = ApplyFilter<Person>(filterField, filterOper, filterValue); 

      var query = from p in people.AsQueryable().Where(oldFamily) 
         select p; 

      Console.ReadLine(); 
     } 

     public static Expression<Func<T, bool>> ApplyFilter<T>(String filterField, String filterOper, String filterValue) 
     { 
      // 
      // Get the property that we are attempting to filter on. If it does not exist then throw an exception 
      System.Reflection.PropertyInfo prop = typeof(T).GetProperty(filterField); 
      if (prop == null) 
       throw new MissingMemberException(String.Format("{0} is not a member of {1}", filterField, typeof(T).ToString())); 

      Expression convertExpression  = Expression.Convert(Expression.Constant(filterValue), prop.PropertyType); 

      ParameterExpression parameter = Expression.Parameter(prop.PropertyType, filterField); 
      ParameterExpression[] parameters = new ParameterExpression[] { parameter }; 
      BinaryExpression body   = Expression.LessThanOrEqual(parameter, convertExpression); 


      Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(body, parameters); 


      return predicate; 

     } 
    } 

    public class Person 
    { 

     public string FirstName { get; set; } 
     public string LastName { get; set; } 
     public DateTime? DateOfBirth { get; set; } 
     string Nickname { get; set; } 
     public int? Weight { get; set; } 

     public Person() { } 
     public Person(string fName, string lName) 
     { 
      FirstName = fName; 
      LastName = lName; 
     } 
    } 
} 

업데이트 : 2013년 2월 1일

내 생각은 비 nullable 형식 버전의에 Nullabe 형식을 변환하는 다음이었다. 따라서이 경우 <Nullable> DateTime을 간단한 DateTime 유형으로 변환하려고합니다. Expression.Convert 호출을 호출하기 전에 다음 코드 블록을 추가하여 Nullable 값의 유형을 결정하고 캡처했습니다.

// 
// 
Type propType = prop.PropertyType; 
// 
// If the property is nullable we need to create the expression using a NON-Nullable version of the type. 
// We will get this by parsing the type from the FullName of the type 
if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 
{ 
    String typeName = prop.PropertyType.FullName; 
    Int32 startIdx = typeName.IndexOf("[[") + 2; 
    Int32 endIdx = typeName.IndexOf(",", startIdx); 
    String type  = typeName.Substring(startIdx, (endIdx-startIdx)); 
    propType  = Type.GetType(type); 
} 

Expression convertExpression = Expression.Convert(Expression.Constant(filterValue), propType); 

이 실제로 날짜 시간에서 Null 허용 다움을 제거하는 일을하지만, 다음과 같은 강제 변환 오류가 발생했습니다. 나는 "Expression.Convert"메서드의 목적이 바로이 작업을 수행하는 것이라고 생각하면서 혼란 스럽습니다.

'System.String'과 'System.DateTime'형식간에 강제 변환 연산자가 정의되어 있지 않습니다. 내가 밀어

명시 적으로, 람다와 나는 식의 가지고있는 지식을 앞지르 예외로 이어지는 ...

DateTime dt = DateTime.Parse(filterValue); 
Expression convertExpression = Expression.Convert(Expression.Constant(dt), propType); 

을 날짜 시간에 값을 구문 분석하고 혼합으로 그 연결 자신의 관련 ILK ... 유형 'System.DateTime'의

ParameterExpression은 'ConsoleApplication1.Person'

유형의 위임 매개 변수에 사용할 수 없습니다 시도할만한 것이 있는지 확신 할 수 없습니다.

답변

4

이진 표현식을 생성 할 때 피연산자가 호환 가능한 형식이어야합니다. 그렇지 않은 경우, 호환 가능할 때까지 하나 또는 둘 모두에서 변환을 수행해야합니다.

기술적으로 DateTimeDateTime?으로 비교할 수 없으므로 컴파일러에서 암시 적으로 하나를 다른 것으로 승격시켜 비교할 수 있습니다. 컴파일러가 표현식을 생성하는 컴파일러가 아니기 때문에 변환을 수행해야합니다.

좀 더 일반화 된 (그리고 작업중 : D) 예제를 수정했습니다.

public static Expression<Func<TObject, bool>> ApplyFilter<TObject, TValue>(String filterField, FilterOperation filterOper, TValue filterValue) 
{ 
    var type = typeof(TObject); 
    ExpressionType operation; 
    if (type.GetProperty(filterField) == null && type.GetField(filterField) == null) 
     throw new MissingMemberException(type.Name, filterField); 
    if (!operationMap.TryGetValue(filterOper, out operation)) 
     throw new ArgumentOutOfRangeException("filterOper", filterOper, "Invalid filter operation"); 

    var parameter = Expression.Parameter(type); 

    var fieldAccess = Expression.PropertyOrField(parameter, filterField); 
    var value = Expression.Constant(filterValue, filterValue.GetType()); 

    // let's perform the conversion only if we really need it 
    var converted = value.Type != fieldAccess.Type 
     ? (Expression)Expression.Convert(value, fieldAccess.Type) 
     : (Expression)value; 

    var body = Expression.MakeBinary(operation, fieldAccess, converted); 

    var expr = Expression.Lambda<Func<TObject, bool>>(body, parameter); 
    return expr; 
} 

// to restrict the allowable range of operations 
public enum FilterOperation 
{ 
    Equal, 
    NotEqual, 
    LessThan, 
    LessThanOrEqual, 
    GreaterThan, 
    GreaterThanOrEqual, 
} 

// we could have used reflection here instead since they have the same names 
static Dictionary<FilterOperation, ExpressionType> operationMap = new Dictionary<FilterOperation, ExpressionType> 
{ 
    { FilterOperation.Equal,    ExpressionType.Equal }, 
    { FilterOperation.NotEqual,    ExpressionType.NotEqual }, 
    { FilterOperation.LessThan,    ExpressionType.LessThan }, 
    { FilterOperation.LessThanOrEqual,  ExpressionType.LessThanOrEqual }, 
    { FilterOperation.GreaterThan,   ExpressionType.GreaterThan }, 
    { FilterOperation.GreaterThanOrEqual, ExpressionType.GreaterThanOrEqual }, 
}; 

그런 다음이 기능을 사용하려면

var filterField = "DateOfBirth"; 
var filterOper = FilterOperation.LessThanOrEqual; 
var filterValue = DateTime.Parse("2000/01/01"); // note this is an actual DateTime object 

var oldFamily = ApplyFilter<Person>(filterField, filterOper, filterValue); 

var query = from p in people.AsQueryable().Where(oldFamily) 
      select p; 

나도 몰라,이 모든 경우에 대해있는 그대로 있지만, 확실히이 특정 사건에 대한 작동 작동합니다.

+0

굉장! 나는 이것이 내가 찾고 있었던 것이라고 생각한다! 이 메소드에 대한 여러 호출의 결과를 단일 표현식으로 결합 할 수 있습니까? 예 : 생일은 2001 년 1 월 1 일 이후이고 이름은 "Lori"인 가족 중 누구입니까? 다시 한 번 감사드립니다! –

+0

단순한'AND'를하고 있다면'Where' 절을 쿼리에 추가하면됩니다. 그것이 'OR'이라면, 절을 하나의 표현식으로 결합해야합니다. 이 경우 PredicateBuilder를 살펴보면 더 쉽게 작업 할 수 있습니다. 너무 복잡해서는 안됩니다. –

+0

고마워 ... 나는이 간단한 게시물에 대한 이전 게시물 http://stackoverflow.com/questions/1922497/how-do-i-combine-linq-expressions-into-one을 찾았습니다. –

1

body 변수를 조사하면 작성중인 표현의 본문이 본질적으로 DateOfBirth <= '2000/01/01'임을 알 수 있습니다.

얼굴이 올바른 것처럼 보일 수도 있지만 Person (예 : T)을 사용하는 함수에 해당 본문을 할당하고 bool을 반환합니다. 본문이 Person 개체로 입력을 반영하고 Person의 해당 인스턴스에서 DateOfBirth 속성에 액세스 한 다음 비교를 수행하도록 논리를 변경해야합니다.

즉, 표현식의 본문에 T을 가져 와서 올바른 속성을 찾아서 비교해야합니다.

+0

나는 이것이 몇 가지 훌륭한 정보 였고 내가 더 많이보고있을 것이라고 말하고 싶었다. 답변 해 주셔서 감사합니다. –

+0

@ GaryO.Stenstrom : @JeffMercado가이 문제를 해결했습니다. 'var fieldAccess = Expression.PropertyOrField (parameter, filterField);'라인을 참조하십시오. 이것은 내가 말하고있는'Person.DateOfBirth' 속성 접근을 생성합니다. – goric

관련 문제