2009-09-23 6 views
19

Dire che ho un DataTable con quattro colonne, Società (stringa), Fondo (stringa), Stato (stringa), Value (doppio):System.LINQ.Dynamic: selezionare ("nuovo (...)") in un elenco <T> (o qualsiasi altra raccolta enumerabile di <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)); 

voglio usare System.LINQ.Dynamic per costruire una query dinamica, che i gruppi su entrambi Company, fondo, o di uno Stato, e quindi seleziona il mio gruppo da criteri come la prima colonna, e la somma (valore):

string groupbyvalue="Fund"; 
var q1= table1.AsEnumerable().AsQueryable() 
       .GroupBy(groupbyvalue,"it") 
       .Select("new ("+groupbyvalue+" as Group, Sum(Value) as TotalValue)"); 

Nella query sopra, la groupbyvalue selezionato (Group) w mal sarà sempre una stringa, e la somma sarà sempre un doppio, quindi voglio essere in grado di eseguire il cast in qualcosa come una List, dove Result è un oggetto con proprietà Group (stringa) e TotalValue (double).

Sto avendo un sacco di problemi con questo, qualcuno può far luce?

risposta

40

In primo luogo, si accede al valore corrente come raggruppati Key nel vostro Seleziona clausola:

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

che dovrebbe rendere il vostro lavoro query. La domanda più difficile è come trasformare gli oggetti restituiti, che avranno un tipo generato dinamicamente che eredita da DynamicClass, in un tipo statico.

Opzione 1: utilizzare la riflessione per accedere alle proprietà Group e TotalValue dell'oggetto dinamico.

Opzione 2: utilizzare alberi di espressioni compilati per generare codice leggero per accedere alle proprietà Group e TotalValue.

Opzione 3: modifica la libreria dinamica per supportare un risultato fortemente tipizzato. Questo risulta essere piuttosto semplice:

  1. In ExpressionParser.Parse(), catturare l'argomento di tipo in un campo privato:

    private Type newResultType; 
    public Expression Parse(Type resultType) 
    { 
        newResultType = resultType; 
        int exprPos = token.pos; 
        // ... 
    
  2. Verso la fine del ExpressionParser.ParseNew(), cercheremo di utilizzare newResultType prima inadempiente per un tipo dinamico:

    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. Infine, abbiamo bisogno di una versione fortemente tipizzato di 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))); 
    } 
    

    Le uniche modifiche rispetto all'originale Select() sono luoghi che fanno riferimento a TResult.

Ora abbiamo solo bisogno di un tipo di nome per tornare:

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

E la query aggiornato sarà simile a questa:

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

Un'altra possibilità è quella di estendere linguaggio di query di DLinq con la possibilità di specificare il nome del tipo nella clausola new nella stringa di query.

Ho descritto la necessità di modifiche in Dynamic.cs in un altro stackoverflow answer.

Essa vi permetterà di porre domande come le seguenti:

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

ho colato tornato dati to List <IExampleInterface> dal seguente senso:

1 Estendere Selezionare il metodo di LINQ dinamica per sostenere generici tipi. Vedere prima risposta here

2 Utilizzare il metodo Select (molto simile alla prima linea di risposta di dahlbyk)

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

Se avete bisogno di più colonne, è possibile utilizzare i seguenti:

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

3 Converti dati da elencare.

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

4 Utilizzare Impromptu per convertire list per elencare <IExampleInterface>

var result = new List<IExampleInterface>(); 

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