Agregar dinámicamente un GroupBy a una expresión Lambda

expression-trees linq

Pregunta

Ok, admitiré que todavía no "entiendo" completamente las expresiones lambda y los árboles de expresiones LINQ; Mucho de lo que hago es cortar, pegar y ver qué funciona. He revisado un montón de documentación, pero todavía no he encontrado el momento "aha".

Con eso dicho...

Estoy intentando agregar dinámicamente una expresión GroupBy a mi expresión Linq. Seguí la pregunta aquí: Necesito ayuda para crear Linq.Expression to Enumerable.GroupBy

Y traté de implementar lo que vi allí.

En primer lugar, tengo clases de entidad para mi base de datos y una tabla llamada ObjCurLocViewNormalized

Tengo un método que hace la llamada inicial,

public IQueryable<ObjCurLocViewNormalized> getLocations()
{
    IQueryable<ObjCurLocViewNormalized> res = (from loc in tms.ObjCurLocViewNormalized
                               select loc);
    return res;
}

para que pueda llamar:

IQueryable<MetAmericanLinqDataModel.ObjCurLocViewNormalized> locations = american.getLocations();

No hay problema hasta ahora.

Ahora, quiero agrupar por una columna arbitraria, con una llamada como esta:

var grouped = locations.addGroupBy(childLocationFieldName);

En este momento, tengo un método:

static public System.Linq.IQueryable<System.Linq.IGrouping<string, TResult>> addGroupBy<TResult>(this IQueryable<TResult> query, string columnName)
{

    var providerType = query.Provider.GetType();
    // Find the specific type parameter (the T in IQueryable<T>)
    var iqueryableT = providerType.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), null).FirstOrDefault();
    var tableType = iqueryableT.GetGenericArguments()[0];
    var tableName = tableType.Name;

    var data = Expression.Parameter(iqueryableT, "query");
    var arg = Expression.Parameter(tableType, tableName);
    var nameProperty = Expression.PropertyOrField(arg, columnName);
    var lambda = Expression.Lambda<Func<TResult, string>>(nameProperty, arg);

    var expression = Expression.Call(typeof(Enumerable), 
                                    "GroupBy", 
                                    new Type[] { tableType, typeof(string) },
                                    data, 
                                    lambda);
    var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
    var result = query.GroupBy(predicate).AsQueryable();
    return result;
}

Todo esto se compila, pero cuando lo ejecuto obtengo el error:

System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[System.String,MetAmericanLinqDataModel.ObjCurLocViewNormalized]]' cannot be used for return type 'System.String'

y el error viene de esta linea:

 var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg);

Estoy copiando y adaptando este código del trabajo exitoso que hice en las cláusulas Where agregadas dinámicamente a una expresión. Así que estoy acuchillando en la oscuridad aquí.

Si alguien puede ayudar a arrojar algo de luz sobre esto, Obviamente, publicar código de trabajo completo y hacer todo lo que pienso por mí sería genial :), pero si pudiera explicar por qué esto está mal, o cómo explicar mi opinión En torno a estos conceptos, eso sería genial. Si puede señalar la documentación que realmente puede ayudar a cerrar la brecha entre los conceptos básicos de las expresiones lambda y la creación de árboles de expresiones dinámicas, sería genial. Obviamente hay grandes agujeros en mi conocimiento, pero creo que esta información podría ser útil para otros.

gracias a todos por su tiempo y, por supuesto, si encuentro la respuesta en otro lugar, la publicaré aquí.

Gracias de nuevo.

Don

Respuesta popular

La solución debe ser bastante simple:

public static IQueryable<IGrouping<TColumn, T>> DynamicGroupBy<T, TColumn>(
    IQueryable<T> source, string column)
{
    PropertyInfo columnProperty = typeof(T).GetProperty(column);
    var sourceParm = Expression.Parameter(typeof(T), "x");
    var propertyReference = Expression.Property(sourceParm, columnProperty);
    var groupBySelector = Expression.Lambda<Func<T, TColumn>>(propertyReference, sourceParm);

    return source.GroupBy(groupBySelector);
}

Suponiendo una clase de muestra como esta:

public class TestClass
{
    public string TestProperty { get; set; }
}

Lo invocas así:

var list = new List<TestClass>();
var queryable = list.AsQueryable();
DynamicGroupBy<TestClass, string>(queryable, "TestProperty");


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow