2013-10-03 25 views
3

我正在構建一個新的擴展方法,它將動態地將來自實體框架的查詢結果分組。如何在運行時創建表達式以用於GroupBy()和Entity Framework?

我已經能夠使用LinqKit構建動態的「where」表達式,但這似乎是一個不同的動物。

預期的新的擴展方法用法:

var results = entities.GroupBy("someFieldName").ToList(); 

擴展方法定義:

public static IQueryable<IGrouping<object, TEntity>> GroupBy<TEntity>(
     this IQueryable<TEntity> source, 
     string fieldName) 
     where TEntity : class, IDataEntity 
    { 
     if (string.IsNullOrEmpty(fieldName)) 
     { 
      return new List<IGrouping<object, TEntity>>().AsQueryable(); 
     } 

     var parameter = Expression.Parameter(typeof(TEntity), "x"); 
     var fieldXExpression = Expression.Property(parameter, fieldName); 
     var lambda = Expression.Lambda<Func<TEntity, object>>(
      Expression.Convert(fieldXExpression, typeof(object)), // throws error when using EF 
      parameter); 
     return source.AsExpandable().GroupBy(lambda); 
    } 

我需要使用Expression.Convert(...),因爲當我使用linqTOobjects測試,代碼失敗時柱爲int32。所以我需要手動將列轉換爲對象,並且效果很好。

現在我正在用EF實體測試它,我猜EF試圖將轉換轉換爲等效的SQL,當然我知道這不存在。

錯誤:

System.NotSupportedException: Unable to cast the type 'System.String' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types. 

有一種方法以產生適合的GroupBy在運行時的表達,這也是兼容EF?

感謝您的任何指導。

+0

Linq to Entities查詢提供程序需要知道要分組的屬性的類型;您需要爲方法調用添加一個泛型類型參數,您必須明確指定,並且可能會首先破壞使用屬性名稱的目的。 – Moho

回答

0

我能夠使用Dynamic Linq完成我所需的工作。我創建了一個名爲ListGroupKeys的擴展方法:

public static List<object> ListGroupKeys<TEntity>(this IQueryable<TEntity> source, string fieldName) 
     where TEntity : class, IDataEntity 
    { 
     var results = source.GroupBy(fieldName, "it").Select("new (KEY as Group)"); 

     var list = new List<object>(); 
     foreach (var result in results) 
     { 
      var type = result.GetType(); 
      var prop = type.GetProperty("Group"); 

      list.Add(prop.GetValue(result)); 
     } 

     return list; 
    } 

這使我可以獲取組鍵值以供後續查詢使用。

0

不幸的是,在實體框架中,您無法通過類型爲object的值進行分組。我認爲有兩種選擇:你可以從方法中返回一個IQueryable(非泛型)對象,如果你正在做一些類似於數據綁定的結果,但是對其他方面來說並不好,那麼這個方法就可以工作。這允許您忽略您正在分組的屬性的類型。

public static IQueryable GroupBy<TEntity>(
    IQueryable<TEntity> source, 
    string fieldName) 
    where TEntity : class, IDataEntity 
{ 
    if (string.IsNullOrEmpty(fieldName)) 
    { 
     return new List<IGrouping<object, TEntity>>().AsQueryable(); 
    } 

    var parameter = Expression.Parameter(typeof(TEntity), "x"); 
    var propertyAccess = Expression.Property(parameter, fieldName); 
    var lambda = Expression.Lambda(propertyAccess, parameter); 

    var groupExpression = Expression.Call(
     typeof(Queryable).GetMethods() 
         .First (x => x.Name == "GroupBy") 
         .MakeGenericMethod(new[]{ typeof(TEntity), propertyAccess.Type }), 
     source.Expression, 
     lambda); 

    var result = source.Provider.CreateQuery(groupExpression); 
    return result; 
} 

IQueryable此方法返回將是正確的類型(如果提到了int財產fieldName,你可以把結果給IQueryable<IGrouping<int,TEntity>>),但它會很難編寫代碼,可以利用這個事實,如果fieldNameint,您將無法將其投射到IQueryable<IGrouping<object,TEntity>>,因爲協變不適用於值類型。

其他選項僅在您知道調用位置的屬性類型時纔可用,以便您可以在方法參數中提供分組鍵的類型(這是您能夠唯一的方法找回正確類型的通用IQueryable):

public static IQueryable<IGrouping<TProperty,TEntity>> GroupBy<TEntity, TProperty>(
    IQueryable<TEntity> source, 
    string fieldName) 
    where TEntity : class, IDataEntity 
{ 
    if (string.IsNullOrEmpty(fieldName)) 
    { 
     return new List<IGrouping<TProperty, TEntity>>().AsQueryable(); 
    } 

    var parameter = Expression.Parameter(typeof(TEntity), "x"); 
    var propertyAccess = Expression.Property(parameter, fieldName); 
    var lambda = Expression.Lambda<Func<TEntity, TProperty>>(propertyAccess, parameter); 

    var result = source.GroupBy (lambda); 
    return result; 
} 
相關問題