2014-01-16 53 views
5

我有以下實例:如何修改表達式<Func <???, bool>>的類型參數?

Expression<Func<IRequiredDate, bool>> 

我想將其轉換爲以下的實例,因此它可以用於運行在實體框架查詢:

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

所以實際做映射的方法不是很難,但遺憾的是沒有一種好的方法可以看出它的推廣。這裏是採用一個Func<T1, TResult>並將其映射到一個代表,其中所述參數是東西比T1多個派生的方法:

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

這使用Replace方法用另一個替換一個表達的所有實例。該定義是:

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種不同類型的Func如果你想,只是創造了每一個參數的表達,並更換所有舊的用新的。這將是乏味的,但只是遵循模式。既然舊的和新的參數類型都需要一個通用的參數,而且沒有辦法推斷參數,那麼......會變得混亂。

+0

這幾乎是我所要求的 - 我會放棄並報告結果。 –

+0

雖然@ felipe的答案實際上是一個更好的解決方案,但我會將其標記爲答案,因爲這直接解決了我所問的問題。但讀者也應該考慮felipe的解決方案。 –

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

表達式樹有一些額外的鑄件,這就是爲什麼在這個表單實體框架告訴你它不能使用IRequiredDate類型的實例。

+0

您能否提供有關此情況的信息? 「類」約束有這種副作用似乎很奇怪。 –

+0

這個解決方案將是我的方案的想法,我會嘗試並讓你知道。 –

+0

我不確定原因。我認爲這與值類型需要被裝箱以便被投入界面但不是參考類型有關。 '((IComparable )「」).CompareTo(「」)'對於類似的'((IComparable )1).CompareTo(0)'不同,int版本需要裝入'this'參數。 – felipe

相關問題