3

我正在构建一个新的扩展方法,它将能够动态地对来自实体框架的查询结果进行分组。

我已经能够使用 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 兼容?

感谢您的任何指导。

4

2 回答 2

1

不幸的是,您不能按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方法返回的类型是正确的(如果fieldName引用一个int属性,你可以将结果强制转换为IQueryable<IGrouping<int,TEntity>>),但是很难编写可以利用这一事实的代码,而且你将无法将其强制转换为IQueryable<IGrouping<object,TEntity>>if fieldNameis an int,因为协方差不适用于值类型。

仅当您从调用位置知道属性的类型时,另一个选项才可用,以便您可以在方法参数中提供分组键的类型(这是您能够正确取回的唯一方法类型化的通用 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;
}
于 2013-10-05T19:52:18.387 回答
1

我能够通过使用 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;
    }

这使我能够获取组键值以在后续查询中使用。

于 2013-10-17T15:00:07.520 回答