Попытка фильтрации по типу 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'
Я не уверен, что осталось попробовать.