2009-09-23 19 views
19

Supongamos que tengo una DataTable con cuatro columnas, Company (string), Fund (string)), Estado (cadena), Valor (doble):System.LINQ.Dynamic: Seleccione ("nuevo (...)") en una lista <T> (o cualquier otra colección enumerable de <T>)

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 construir una consulta dinámica que agrupa a cada compañía, fondo, o del Estado, y se selecciona por mi grupo 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, la groupbyvalue seleccionado (Group) w siempre será una cadena, y la suma siempre será un doble, por lo que quiero poder convertirlo en algo así como una lista, donde el resultado es un objeto con propiedades Grupo (cadena) y TotalValor (doble).

Estoy teniendo un montón de problemas con esto, ¿alguien puede arrojar algo de luz?

Respuesta

40

En primer lugar, podrás acceder al valor actual agrupados como Key en su cláusula Select:

.Select("new (Key as Group, Sum(Value) as TotalValue)"); 

Eso debería hacer su trabajo de consulta. La pregunta más difícil es cómo convertir los objetos devueltos, que tendrán un tipo generado dinámicamente que hereda de DynamicClass, en un tipo estático.

Opción 1: Utilice la reflexión para acceder a las propiedades Group y TotalValue del objeto dinámico.

Opción 2: Utilice árboles de expresión compilados para la generación de código liviano para acceder a las propiedades Group y TotalValue.

Opción 3: modifique la biblioteca dinámica para que admita un resultado de tipado fuerte. Esto resulta ser bastante simple:

  1. En ExpressionParser.Parse(), capturar el argumento de tipo en un campo privado:

    private Type newResultType; 
    public Expression Parse(Type resultType) 
    { 
        newResultType = resultType; 
        int exprPos = token.pos; 
        // ... 
    
  2. Cerca del final de ExpressionParser.ParseNew(), vamos a tratar de utilizar newResultType antes de morosos a un tipo dinámico:

    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); 
    } 
    
  3. Por último, necesitamos una versión estricta de tipos 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 respecto al original Select() son lugares a los que hacen referencia a TResult.

Ahora sólo tenemos un tipo llamado para volver:

public class Result 
    { 
     public string Group { get; set; } 
     public double TotalValue { get; set; } 
    } 

Y su consulta actualizada se verá así:

IQueryable<Result> res = table1.AsQueryable() 
     .GroupBy(groupbyvalue, "it") 
     .Select<Result>("new (Key as Group, Sum(Value) as TotalValue)"); 
1

Otra posibilidad es extender el lenguaje de consulta de DLINQ con la posibilidad de especificar el nombre de tipo en la cláusula new en la cadena de consulta.

He descrito los cambios necesarios en Dynamic.cs en otro stackoverflow answer.

Permitirá que poses consultas como los siguientes:

IQueryable<Result> res 
    = table1.AsQueryable() 
    .GroupBy(groupbyvalue, "it") 
    .Select("new Result(Key as Group, Sum(Value) as TotalValue)") 
    as IQueryable<Result>; 
3

I Casted los datos devueltos a la lista <IExampleInterface> de la siguiente manera:

1 Ampliar Elija un método de LINQ dinámica para apoyar genérica tipos. Ver primera respuesta here

2 Utilice el método Select (muy similar a la primera línea de la respuesta de dahlbyk)

Select<dynamic>("new (Key as Group, Sum(Value) as TotalValue)") 

Si necesita varias columnas, puede utilizar lo siguiente:

GroupBy(@"new (Fund, Column1, Column2)", "it"). 
Select<dynamic>("new (Key.Fund, Key.Column1, Key.Column2, Sum(Value) as TotalValue)") 

3 Convert datos para listar.

var data = ... 
    GroupBy("...").Select<dynamic>("..."). 
    Cast<dynamic>().AsEnumerable().ToList(); 

4 Uso Impromptu para convertir a la lista Lista <IExampleInterface>

var result = new List<IExampleInterface>(); 

foreach (var d in data) 
{ 
    dynamic r = Impromptu.ActLike<IExampleInterface>(d); 
    result.Add(r); 
} 
Cuestiones relacionadas