scottgu - linq expression builder
System.LINQ.Dynamic: Seleccione("nuevo(...)") en una Lista<T>(o cualquier otra colección enumerable de<T>) (4)
Digamos que tengo un DataTable con cuatro columnas, Compañía (cadena), Fondo (cadena), Estado (cadena), Valor (doble):
table1.Rows.Add("Company 1","Fund 1","NY",100));
table1.Rows.Add("Company 2","Fund 1","CA",200));
table1.Rows.Add("Company 3","Fund 1","FL",300));
table1.Rows.Add("Company 4","Fund 2","CA",400));
table1.Rows.Add("Company 5","Fund 1","NY",500));
table1.Rows.Add("Company 6","Fund 2","CA",600));
table1.Rows.Add("Company 7","Fund 3","FL",700));
Quiero usar System.LINQ.Dynamic para crear una consulta dinámica que agrupe a la Compañía, al Fondo o al Estado, y luego seleccione mi grupo por criterios como la primera columna y la suma (valor):
string groupbyvalue="Fund";
var q1= table1.AsEnumerable().AsQueryable()
.GroupBy(groupbyvalue,"it")
.Select("new ("+groupbyvalue+" as Group, Sum(Value) as TotalValue)");
En la consulta anterior, el valor de grupo seleccionado (Grupo) siempre será una cadena, y la suma siempre será doble, por lo que quiero poder convertir en algo como una Lista, donde Resultado es un objeto con propiedades Grupo (cadena ) y TotalValue (doble).
Estoy teniendo muchos problemas con esto, ¿alguien puede arrojar algo de luz?
Otra forma más fácil de hacerlo es usar Json serialización / deserialización utilizando el Newtonsoft.Json de esta manera:
Tener una clase con las propiedades deseadas:
public class MyClass
{
public string Group;
public double Total;
}
Después de su consulta, Json-serialize luego deserializa al tipo de datos que desea:
string jsonData = JsonConvert.SerializeObject(q1);
List<MyClass> typedResult = JsonConvert.DeserializeObject<List<MyClass>>(jsonData);
Otra posibilidad es extender el lenguaje de consulta de DLinq con la posibilidad de especificar el nombre de tipo en la new
cláusula en la cadena de consulta.
He descrito la necesidad de cambios en Dynamic.cs en otra respuesta de .
Te permitirá plantear consultas como las siguientes:
IQueryable<Result> res
= table1.AsQueryable()
.GroupBy(groupbyvalue, "it")
.Select("new Result(Key as Group, Sum(Value) as TotalValue)")
as IQueryable<Result>;
Primero, accederá al valor agrupado actual como Key
en su cláusula de selección:
.Select("new (Key as Group, Sum(Value) as TotalValue)");
Eso debería hacer que su consulta funcione. La pregunta más difícil es cómo convertir los objetos devueltos, que tendrán un tipo generado dinámicamente que se hereda de DynamicClass
, en un tipo estático.
Opción 1: Utilice la reflexión para acceder a las propiedades de Group
y TotalValue
del objeto dinámico.
Opción 2: Utilice árboles de expresiones compiladas para la generación de código ligero para acceder a las propiedades de Group
y TotalValue
.
Opción 3: modifique la biblioteca dinámica para que admita un resultado fuertemente tipado. Esto resulta bastante simple:
En
ExpressionParser.Parse()
, capture el argumento de tipo en un campo privado:private Type newResultType; public Expression Parse(Type resultType) { newResultType = resultType; int exprPos = token.pos; // ...
Cerca del final de
ExpressionParser.ParseNew()
, intentaremos usarnewResultType
antes de establecer un tipo dinámico de formanewResultType
:Expression ParseNew() { // ... NextToken(); Type type = newResultType ?? DynamicExpression.CreateClass(properties); MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); return Expression.MemberInit(Expression.New(type), bindings); }
Finalmente, necesitamos una versión fuertemente tipada de
Select()
:public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(TResult), selector, values); return source.Provider.CreateQuery<TResult>( Expression.Call( typeof(Queryable), "Select", new Type[] { source.ElementType, typeof(TResult) }, source.Expression, Expression.Quote(lambda))); }
Los únicos cambios con respecto al
Select()
original son los lugares a los que hacemos referenciaTResult
.
Ahora solo necesitamos un tipo con nombre para devolver:
public class Result
{
public string Group { get; set; }
public double TotalValue { get; set; }
}
Y tu consulta actualizada se verá así:
IQueryable<Result> res = table1.AsQueryable()
.GroupBy(groupbyvalue, "it")
.Select<Result>("new (Key as Group, Sum(Value) as TotalValue)");
Puse los datos devueltos a la lista <IExampleInterface> de la siguiente manera:
1 Extender Seleccione el método de linq dinámico para admitir tipos genéricos. Vea la primera respuesta here
2 Usa el método de selección (muy similar a la primera línea de la respuesta de dahlbyk)
Select<dynamic>("new (Key as Group, Sum(Value) as TotalValue)")
Si necesitas varias columnas, puedes usar lo siguiente:
GroupBy(@"new (Fund, Column1, Column2)", "it").
Select<dynamic>("new (Key.Fund, Key.Column1, Key.Column2, Sum(Value) as TotalValue)")
3 Convertir datos a la lista.
var data = ...
GroupBy("...").Select<dynamic>("...").
Cast<dynamic>().AsEnumerable().ToList();
4 Utilice Impromptu para convertir Lista en Lista <IExampleInterface>
var result = new List<IExampleInterface>();
foreach (var d in data)
{
dynamic r = Impromptu.ActLike<IExampleInterface>(d);
result.Add(r);
}