2016-08-16 65 views
3

我需要實現基於擴展方法的API(即,我必須使用靜態非泛型類)。 API應該與LINQ流利的API一起工作,並且主要使用IQueryable參數。就像這樣:可選泛型參數的擴展方法

public static class SomeExtensions 
{ 
    public static IQueryable<TEntity> SomeMethod<TEntity>(this IQueryable<TEntity> set, ... some arguments) 
    { 
    } 
} 

現在,假設該方法應該採取一些參數加上Expression<Func<TEntity, TResult>>之一:

public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
     this IQueryable<TEntity> set, 
     ..., 
     Expression<Func<TEntity, TResult>> orderByExpression) 
    { 
    } 

我想通過orderByExpression以流暢的API的排序依據的方法。或者如果orderByExpression == null做一些其他的事情。

當然,我想有這樣的事情:

public static IQueryable<TEntity> SomeMethod<TEntity, TResult>(
     this IQueryable<TEntity> set, 
     ..., 
     Expression<Func<TEntity, TResult>> orderByExpression = null) 
    { 
    } 

...但調用此方法W/O可選參數我要含蓄通過泛型類型,當因爲編譯器不知道TResult的類型。

我看到一些可能的方法,但我不太喜歡它們。

  1. 定義兩個方法:一個用這個參數和一個w/o,並從第二個調用第一個。我不喜歡它,因爲實際上,API中有很多這樣的方法,我必須爲它們中的每一個定義一個附加方法。

  2. 使用Expression<Func<TEntity, object>>而不是Expression<Func<TEntity, TResult>>(現在是這樣)。我擺脫了泛型類型,但是像int這樣的簡單(值)類型存在問題:在嘗試將System.Int32強制轉換爲System.Object時,LINQ會引發異常。

  3. 也許(還沒有嘗試過)我可以使用Expression<Func<TEntity, dynamic>> - 但我不認爲這是一個好方法。

任何其他的想法,任何人?

+1

'Enumerable'和'Queryable'類是設計這種API的好例子。解決方案是選項(2),無論您需要添加多少重載。 –

+2

@IvanStoev你不是說選項(1)? – Kapol

+0

@卡波爾絕對!一個愚蠢的打字錯誤,謝謝。 –

回答

2

從調用者的角度來看,選項(1)是最好的。記住API的主要目標是讓調用者的生活更輕鬆,因此在實現方面進行額外的努力應該足夠值錢。

選項(3)不好。您不想輸入由dynamic類型引入的併發症。而EF不喜歡動態表情。

選項(2)其實並不那麼糟糕。所以,如果這是你目前使用的,你可以留在它上面。所有你需要讓EF高興的是通過移除爲值類型屬性引入的Convert來轉換傳遞的表達式。要做到這一點,你可以用下面的輔助方法:

internal static IQueryable<T> ApplyOrderBy<T>(
    this IQueryable<T> source, 
    Expression<Func<T, object>> orderByExpression = null) 
{ 
    if (orderByExpression == null) return source; 
    var body = orderByExpression.Body; 
    // Strip the Convert if any 
    if (body.NodeType == ExpressionType.Convert) 
     body = ((UnaryExpression)body).Operand; 
    // Create new selector 
    var keySelector = Expression.Lambda(body, orderByExpression.Parameters[0]); 
    // Here we cannot use the typed Queryable.OrderBy method because 
    // we don't know the TKey, so we compose a method call instead 
    var queryExpression = Expression.Call(
     typeof(Queryable), "OrderBy", new[] { typeof(T), body.Type }, 
     source.Expression, Expression.Quote(keySelector)); 
    return source.Provider.CreateQuery<T>(queryExpression); 
} 

這裏是在小的考驗是如何爲不同的物業類型上述作品:

var input = new[] 
{ 
    new { Id = 2, Name = "B", ParentId = (int?)1 }, 
    new { Id = 1, Name = "A", ParentId = (int?)null }, 
}.AsQueryable(); 

var output1 = input.ApplyOrderBy(e => e.Id).ToList(); 
var output2 = input.ApplyOrderBy(e => e.Name).ToList(); 
var output3 = input.ApplyOrderBy(e => e.ParentId).ToList(); 

樣品使用你的榜樣:

public static IQueryable<TEntity> SomeMethod<TEntity>(
    this IQueryable<TEntity> source, 
    ..., 
    Expression<Func<TEntity, object>> orderByExpression = null) 
{ 
    var result = source; 
    result = preprocess(result); 
    result = result.ApplyOrderBy(orderByExpression); 
    result = postprocess(result); 
    return result;  
} 
+0

現在,_this_看起來很有趣。我一定會試一試,非常感謝! –

0

您指定的第一個選項是顯而易見且最乾淨的,儘管這是最重的維護方式。

另外,您可以在流利的語法中引入另一個步驟。像限定:

public interface ISortableQueryable<T> : IQueryable<T> 
{ 
    IQueryable<T> WithSorting<TResult>(Expression<Func<TEntity, TResult>> orderByExpression); 
} 

返回它:

public static ISortableQueryable<TEntity> SomeMethod<TEntity>(
    this IQueryable<TEntity> @this, ...) 
    { ... } 

,並提供實現這個接口,其中定期IQueryable電話或者重定向到它接收的構造函數,或一些邏輯是基於這樣的事實進行的IQueryable實例是否調用WithSorting方法。