2

テストアプリ全体を以下に貼り付けました。かなりコンパクトなので問題ないことを期待しています。切り取ってコンソールアプリに貼り付けて実行できるはずです。

1つ以上のPersonオブジェクトのプロパティをフィルタリングできる必要がありますが、実行時までどのプロパティかわかりません。これはあちこちで議論されていることを知っています。PredicateBuilderやDynamicLinqLibraryなどのツールも調べて使用していますそれらの議論は並べ替えと順序付けに重点を置く傾向があり、それぞれが苦労しています。 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型をNon-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);

これは実際にはDateTimeからNullable-nessを削除する際に機能しましたが、次の強制エラーが発生しました。「Expression.Convert」メソッドの目的はまさにこれを行うことだと思っていたので、私はこれに混乱し続けています。

タイプ「System.String」と「System.DateTime」の間に強制演算子は定義されていません。

プッシュすると、値を明示的にDateTimeに解析し、それをミックスにプラグインしました...

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

...これは、式、ラムダ、およびそれらに関連する同類についての私が持っている知識を上回る例外をもたらしました...

タイプ「System.DateTime」のParameterExpressionは、タイプ「ConsoleApplication1.Person」のデリゲートパラメータには使用できません。

何を試すべきかわかりません。

4

2 に答える 2

6

問題は、バイナリ式を生成するとき、オペランドは互換性のあるタイプでなければならないということです。そうでない場合は、互換性があるまで一方(または両方)で変換を実行する必要があります。

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;

これがすべての場合にそのまま機能するかどうかはわかりませんが、この特定の場合には確かに機能します。

于 2013-02-02T09:40:20.593 に答える
1

body変数を調べると、作成している式の本体が基本的にであることがわかりますDateOfBirth <= '2000/01/01'

一見これは正しいように見えるかもしれませんが、あなたはその本体をPersonTあなたの例ではそうなるでしょう)を取り、ブール値を返す関数に割り当てようとしています。本体が入力をオブジェクトとして反映し、のそのインスタンスのプロパティにPersonアクセスしてから比較を実行するように、ロジックを変更する必要があります。DateOfBirthPerson

言い換えると、式の本体は、を取り、Tその上で適切なプロパティを見つけてから比較する必要があります。

于 2013-02-02T01:09:02.400 に答える