我已经在下面粘贴了我的整个测试应用程序。它相当紧凑,所以我希望这不是问题。您应该能够简单地将其剪切并粘贴到控制台应用程序中并运行它。
我需要能够过滤 Person 对象的任何一个或多个属性,直到运行时我才知道是哪个(两个)。我知道这已经到处讨论过,我已经研究过并且正在使用诸如PredicateBuilder和Dynamic 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 类型转换为其不可为 null 的类型版本。因此,在本例中,我们希望将
//
//
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);
这实际上有效地从日期时间中删除了可为空性,但导致了以下强制错误。我仍然对此感到困惑,因为我认为"Expression.Convert"方法的目的是这样做。
在类型"System.String"和"System.DateTime"之间没有定义强制运算符。
继续推动我明确地将值解析为 DateTime 并将其插入到组合中......
DateTime dt = DateTime.Parse(filterValue);
Expression convertExpression = Expression.Convert(Expression.Constant(dt), propType);
。这导致了一个例外,超过了我对表达式、Lambda 及其相关同类的任何了解......
类型为"System.DateTime"的参数表达式不能用于类型为"控制台应用程序1.Person"的委托参数
我不确定还有什么要尝试的。
问题是在生成二进制表达式时,操作数必须是兼容的类型。 如果没有,则需要对一个(或两个)执行转换,直到它们兼容。
从技术上讲,您无法将DateTime
与DateTime?
进行比较,编译器隐式地将一个提升到另一个,这使我们能够进行比较。 由于编译器不是生成表达式的编译器,因此我们需要自己执行转换。
我已经调整了您的示例以使其更通用(并且: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;
我不知道这是否适用于所有情况,但它肯定适用于这种特殊情况。
如果你询问你的body
变量,你可以看到你正在创建的表达式的主体本质上是DateOfBirth <= '2000/01/01'
。
虽然从表面上看这似乎是正确的,但您正在尝试将该主体分配给一个函数,该函数采用Person
(这就是示例中T
的内容)并返回布尔值。您需要更改逻辑,以便主体将输入反映为 Person
对象,访问该Person
实例上的 DateOfBirth
属性,然后执行比较。
换句话说,你的表达式的主体必须采取T
,找到正确的属性,然后进行比较。