Попытка фильтрации по типу Nullable с помощью деревьев выражений

Я вставил все мое тестовое приложение ниже. Это довольно компактно, поэтому я надеюсь, что это не проблема. Вы должны иметь возможность просто вырезать и вставить его в консольное приложение и запустить его.

Мне нужно иметь возможность фильтровать одно или несколько свойств объектов Person, и я не знаю, какие из них до времени выполнения. Я знаю, что это обсуждалось повсеместно, и я изучил и использую такие инструменты, какPredicateBuilder & Динамическая библиотека Linq но обсуждение вокруг них имеет тенденцию сосредотачиваться больше на сортировке и упорядочении, и каждый из них боролся со своими проблемами, когда сталкивался с типами Nullable. Поэтому я подумал, что постараюсь создать хотя бы дополнительный фильтр, который бы учитывал эти конкретные сценарии.

В приведенном ниже примере я пытаюсь отфильтровать членов семьи, которые родились после определенной даты. Дело в том, что поле DateOfBirth на фильтруемых объектах является свойством DateTime.

Последняя ошибка, которую я получаю,

Оператор приведения не определен между типами 'System.String' и 'System.Nullable`1 [System.DateTime]'.

В чем проблема. Я пытался использовать несколько разных способов приведения и конвертации, но с разной степенью неудачи. В конечном итоге это будет применено к базе данных EF, которая также отказывается от методов преобразования, таких как DateTime.Parse (-).

Любая помощь будет принята с благодарностью!

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/02/01

Тогда я подумал о том, чтобы преобразовать тип Nullabe в версию без Nullable. Поэтому в этом случае мы хотим преобразовать <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);

Это фактически сработало при удалении Nullable-ness из DateTime, но привело к следующей ошибке Принуждения. Меня это смущает, так как я думал, что целью метода «Expression.Convert» было сделать именно это.

Оператор приведения не определен между типами System.String и System.DateTime.

Нажав на кнопку, я явно проанализировал значение в DateTime и включил его в микс ...

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

... что привело к исключению, которое превосходит все мои знания о выражениях, лямбдах и их родственных связях ...

ParameterExpression типа 'System.DateTime' нельзя использовать для параметра делегата типа 'ConsoleApplication1.Person'

Я не уверен, что осталось попробовать.

Ответы на вопрос(2)

Ваш ответ на вопрос