尝试使用表达式树过滤一个Nullable类型 [英] Trying to filter on a Nullable type using Expression Trees

查看:586
本文介绍了尝试使用表达式树过滤一个Nullable类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在下面粘贴了我的整个测试应用程序。它相当紧凑,所以我希望这不是一个问题。你应该能够简单地剪切并粘贴到控制台应用程序并运行它。

I have pasted my entire test app below. It's fairly compact so I am hoping that it's not a problem. You should be able to simply cut and paste it into a console app and run it.

我需要能够在Person对象的属性中的任何一个或多个过滤器,我不知道哪个(些),直到运行时。我知道,这有木珠所有讨论的地方,我已经看着和我也用的工具,如的 PredicateBuilder &的动态的LINQ库但aroung他们的讨论往往更侧重于分类和排序,每个已在面对Nullable类型时,会遇到自己的问题。所以我想我会尝试建立至少一个辅助过滤器,可以解决这些特定的场景。

I need to be able to filter on any one or more of the Person objects' properties, and I don't know which one(s) until runtime. I know that this has beed discussed all over the place and I have looked into and am also using tools such as the PredicateBuilder & Dynamic Linq Library but the discussion aroung them tends to focus more on Sorting and ordering, and each have been struggling with their own issues when confronted with Nullable types. So I thought that I would attempt to build at least a supplemental filter that could address these particular scenarios.

在下面的示例中,我将筛选在特定日期后出生的家庭成员。开始的是,正在被过滤的对象上的DateOfBirth字段是一个DateTime属性。

In the example below I am trying to filter out the family members who were born after a certain date. The kick is that the DateOfBirth field on the objects being filterd is a DateTime property.

我得到的最新误差

没有强制经营者之间的定义类型'System.String'和'System.Nullable`1 [System.DateTime的]。

No coercion operator is defined between types 'System.String' and 'System.Nullable`1[System.DateTime]'.

这是问题。我尝试了几种不同的铸造和转换方法,但尝试了不同程度的失败。最终,这将应用于EF数据库,同时也阻止了转换方法,如DateTime.Parse( - )。

Which is the problem. I have attempted several different means of casting and converting but to varying degrees of failure. Ultimately this will be applied against an EF database whcih has also balked at conversion methods such as DateTime.Parse(--).

任何帮助都将非常感激。

Any assistence would be greatly appreciated!

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类型转换为Non-Nullable类型版本。因此,在这种情况下,我们要将< Nullable> DateTime转换为一个简单的DateTime类型。我在调用Expression.Convert调用之前添加了以下代码块,以确定并捕获Nullable值的类型。

My thought was then to convert the Nullabe type to it's Non-Nullable type version. So in this case we want to convert the <Nullable>DateTime to a simple DateTime type. I added the following code block before the call Expression.Convert call to determine and capture the type of the Nullable value.

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

这实际上在从DateTime中删除Nullable时,但是导致以下强制错误。我仍然对此感到困惑,因为我认为Expression.Convert方法的目的是做这个。

This actually worked in removing the Nullable-ness from the DateTime but resulted in the following Coercion error. I remain confused by this as I thought that the purpose of the "Expression.Convert" method was to do just this.

没有强制操作符在类型之间定义'System.String'和'System.DateTime'。

No coercion operator is defined between types 'System.String' and 'System.DateTime'.

推送我明确地将值解析为一个DateTime并插入到混合...

Pushing on I explicitly parsed the value to a DateTime and plugged that into the mix ...

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

...这导致了一个例外,超出了我对表达式,Lambdas及其相关ilk ...

... which resulted in an exception that outpaces any knowledge I have of Expressions, Lambdas and their related ilk ...

类型System.DateTime的ParameterExpression不能用于ConsoleApplication1.Person类型的委托参数

ParameterExpression of type 'System.DateTime' cannot be used for delegate parameter of type 'ConsoleApplication1.Person'

我不知道还有什么要试试。

I am not sure what's left to try.

推荐答案

问题是,当生成二进制表达式时,操作数必须是兼容的类型。

The problem is that when generating binary expressions, the operands have to be of compatible types. If not, you need to perform a conversion on one (or both) until they are compatible.

在技术上,你不能比较 DateTime

Technically, you cannot compare a DateTime with a DateTime?, the compiler implicitly promotes one to the other which allows us to do our comparisons. Since the compiler is not the one generating the expression, we need to perform the conversion ourself.

我已经调整你的例子更一般(和工作:D) 。

I've tweaked your example to be more general (and working :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;

我不知道这是否会按原样工作,特殊情况。

I don't know if this will work as-is for all cases but it certainly works for this particular case.

这篇关于尝试使用表达式树过滤一个Nullable类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆