2014-01-16 2 views
5

私は、次のインスタンスを持っている:Expression <Func <???, bool>>の型パラメータを変更する方法は?

Expression<Func<IRequiredDate, bool>> 

Entity Frameworkの中でクエリを実行するために使用することができますので、私は、次のインスタンスに変換したい:

Expression<Func<TModel, bool>> 

この例えば、私はIRequiredDateを実装する任意のモデルに一般的なフィルタリング・クエリーを活用できるようになります。:

// In some repository function: 
var query = DbContext.Set<Order>() 
    .FilterByDateRange(DateTime.Today, DateTime.Today); 

var query = DbContext.Set<Note>() 
    .FilterByDateRange(DateTime.Today, DateTime.Today); 

var query = DbContext.Set<Complaint>() 
    .FilterByDateRange(DateTime.Today, DateTime.Today); 


// The general purpose function, can filter for any model implementing IRequiredDate 
public static IQueryable<TModel> FilterByDate<TModel>(IQueryable<TModel> query, DateTime startDate, DateTime endDate) where TModel : IRequiredDate 
{ 
    // This will NOT WORK, as E/F won't accept an expression of type IRequiredDate, even though TModel implements IRequiredDate 
    // Expression<Func<IRequiredDate, bool>> dateRangeFilter = x => x.Date >= startDate && x.Date <= endDate; 
    // query = query.Where(dateRangeFilter); 

    // This also WON'T WORK, x.Date is compiled into the expression as a member of IRequiredDate instead of TModel, so E/F knocks it back for the same reason: 
    // Expression<Func<TModel, bool>> dateRangeFilter = x => x.Date >= startDate && x.Date <= endDate; 
    // query = query.Where(dateRangeFilter); 

    // All you need is lov.... uh... something like this: 
    Expression<Func<IRequiredDate, bool>> dateRangeFilter = x => x.Date >= startDate && x.Date <= endDate; 
    Expression<Func<TModel, bool>> dateRangeFilterForType = ConvertExpressionType<IRequiredDate, TModel>(dateRangeFilter); // Must convert the expression from one type to another 
    query = query.Where(dateRangeFilterForType) // Ahhhh. this will work. 

    return query; 
} 

public static ConvertExpressionType<TInterface, TModel>(Expression<Func<TInterface, bool>> expression) 
where TModel : TInterface // It must implement the interface, since we're about to translate them 
{ 
    Expression<Func<TModel, bool>> newExpression = null; 

    // TODO: How to convert the contents of expression into newExpression, modifying the 
    // generic type parameter along the way?? 

    return newExpression; 
} 

私は、彼らはさまざまな種類であり、できないことを理解キャストする。しかし、新しいExpression<Func<TModel, bool>>を作成し、提供されたExpression<Func<IRequiredDate, bool>>の内容に基づいてそれを再構築する方法があるかどうか疑問に思っています。どのタイプの参照もIRequiredDateからTModelに切り替えています。

これはできますか?

+0

質問の理解に問題があります。あなたは具体的な例を挙げることができますか? – Dai

+0

TModelジェネリック型パラメータですか? –

+0

上記のサンプルコードを質問に追加しました。 –

答えて

7

実際にマッピングを実行する方法はであり、それはですが、悲しいことに、私はそれを一般化することができます。これは別と1つの式のすべてのインスタンスを置き換えるためにReplaceメソッドを使用しています

public static Expression<Func<NewParam, TResult>> Foo<NewParam, OldParam, TResult>(
    Expression<Func<OldParam, TResult>> expression) 
    where NewParam : OldParam 
{ 
    var param = Expression.Parameter(typeof(NewParam)); 
    return Expression.Lambda<Func<NewParam, TResult>>(
     expression.Body.Replace(expression.Parameters[0], param) 
     , param); 
} 

:ここFunc<T1, TResult>を取り、パラメータがよりT1より派生何かあるデリゲートにマップする方法があります。定義は次のとおりです。

internal class ReplaceVisitor : ExpressionVisitor 
{ 
    private readonly Expression from, to; 
    public ReplaceVisitor(Expression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 
    public override Expression Visit(Expression node) 
    { 
     return node == from ? to : base.Visit(node); 
    } 
} 

public static Expression Replace(this Expression expression, 
    Expression searchEx, Expression replaceEx) 
{ 
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); 
} 

今、私たちはそうのように(よりよい名前が与えられなければならない)、このメソッドを使用することができます:

Expression<Func<object, bool>> oldExpression = whatever; 
Expression<Func<string, bool>> newExpression = 
    Foo<string, object, bool>(oldExpression); 

そしてもちろんのFuncはそのパラメータに関して、実際に共変であることから、このメソッドを呼び出すと、ランタイムのエラーポイントを追加しない式が生成されることが確認できます。

Func<T1, T2, TResult>のようなバージョンを作ることができます。必要ならば16種類の異なるタイプのなどを作成して、それぞれのパラメータ式を作成して古いものをすべて新しいものに置き換えることができます。面倒ですが、パターンにちょうど従います。古い型と新しい型の両方の型の汎用的な引数が必要であり、引数を推測する方法がないということを考えれば、それは...面倒です。

+0

これは私が求めていたものです。私はそれを行って結果を報告します。 –

+0

@フェリペの答えは実際に私のシナリオのためのより良い解決策ですが、私はこれを答えとしてマークします。しかし、読者は間違いなくフェリペのソリューションも考慮する必要があります。 –

-1

私は数分しかなかったので、私はこれについて深く考えなかった。これは役に立ちますか?

Expression<Func<IList, bool>> exp1 = (list => list.Count > 0); 
Expression<Func<string[], bool>> exp2 = (list => exp1.Compile()(list)); 
Expression<Func<List<int>, bool>> exp3 = (list => exp1.Compile()(list)); 

私はあなたが思っているものを少しは実証します。

+1

提案していただきありがとうございますが、これらの式はエンティティ・フレームワークになり、むしろメモリ内でコンパイルされて実行されるため、このソリューションは動作しません。 –

3

幸いにも、あなたが望むものは、表現木で遊ぶ必要はありません。あなたが必要とするのは、テンプレート制限を強化することです:

public static IQueryable<TModel> FilterByDate<TModel>(this IQueryable<TModel> src, DateTime startDate, DateTime endDate) where TModel: class, IRequiredDate { 
    return src.Where(x => x.Date >= startDate && x.Date <= endDate); 
} 

説明のビット。 LINQPadを使用すると、classの要件が削除されたときに生成される式ツリーが異なることがわかります。制限が存在する場合Where句は、このようなものです:

.Where (x => (x => x.Date >= startDate && x.Date <= endDate)) 

のに対し、以下のような制限は、発現の変化を削除されたとき:

.Where (x => (x => (((IRequiredDate)x).Date >= startDate) && (((IRequiredDate)x).Date <= endDate))) 

式ツリーは、いくつかの余分なキャストを持って、なぜですこのフォームのEntity Frameworkは、タイプIRequiredDateのインスタンスでは動作しないことを示します。

+0

これがなぜそのような場合の洞察を提供できますか? 'class'制約がこの副作用を持つことは奇妙に思えます。 –

+0

この解決策は私のシナリオにとっては理想的なことであり、私はそれを試し、あなたに知らせるでしょう。 –

+0

私は原因についてはわかりません。私はそれがインターフェイスにキャストされるために値の型を囲む必要があるが、参照の型はそうでないという事実に関連していると思います。同様の '((IComparable )1).CompareTo(0)'では、(IComparable ) ").CompareTo(" ")'のILは異なります。intバージョンでは、 'this'パラメータのボクシングが必要です。 – felipe

関連する問題