2009-09-23 6 views
19

Dites que j'ai un DataTable avec quatre colonnes, Company (chaîne), Fund (string), État (chaîne), Valeur (double):System.LINQ.Dynamic: Sélectionnez ("new (...)") dans une liste <T> (ou toute autre collection énumérable 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)); 

Je veux utiliser System.LINQ.Dynamic pour construire une requête dynamique des groupes de chaque société, Fonds, ou de l'État, puis sélectionne mon groupe par critères que la première colonne, et la somme (valeur):

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

Dans la demande de recherche ci-dessus, le (Group) groupbyvalue choisi w Je serai toujours une chaîne, et la somme sera toujours un double, donc je veux être capable de lancer quelque chose comme une liste, où Result est un objet avec les propriétés Group (string) et TotalValue (double).

J'ai beaucoup de problèmes avec cela, quelqu'un peut-il faire la lumière?

Répondre

40

D'abord, vous aurez accès à la valeur groupée actuelle comme Key dans votre clause Select:

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

Cela devrait rendre votre travail de recherche. La question plus difficile est de savoir comment activer les objets retournés, qui auront un type généré dynamiquement qui hérite de DynamicClass, dans un type statique.

Option 1: utilisez la réflexion pour accéder aux propriétés Group et TotalValue de l'objet dynamique.

Option 2: Utilisez les arborescences d'expression compilées pour la génération de code allégée afin d'accéder aux propriétés Group et TotalValue.

Option 3: Modifiez la bibliothèque dynamique pour prendre en charge un résultat fortement typé. Cela se révèle être assez simple:

  1. En ExpressionParser.Parse(), saisir l'argument de type dans un domaine privé:

    private Type newResultType; 
    public Expression Parse(Type resultType) 
    { 
        newResultType = resultType; 
        int exprPos = token.pos; 
        // ... 
    
  2. Près de la fin de ExpressionParser.ParseNew(), nous allons essayer d'utiliser newResultType avant en défaut à un type dynamique:

    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. Enfin, nous avons besoin d'une version fortement typée 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))); 
    } 
    

    Les seules modifications de la Select() d'origine sont des lieux nous faisons référence TResult.

Maintenant, nous avons juste besoin d'un type nommé à retourner:

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

Et votre requête mise à jour ressemblera à ceci:

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

Une autre possibilité est d'étendre le langage de requête de DLinq avec la possibilité de spécifier le nom du type dans la clause new dans la chaîne de requête.J'ai décrit les changements nécessaires dans Dynamic.cs dans un autre stackoverflow answer.

Il vous permet de poser des questions comme les suivantes:

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

Je Casted les données retournées à la liste <IExampleInterface> par la manière suivante:

1 Extend Sélectionner la méthode de LINQ dynamique pour soutenir générique les types. Voir la première réponse here

2 Utilisez la méthode Select (très similaire à la première ligne de la réponse de dahlbyk)

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

Si vous avez besoin de plusieurs colonnes, vous pouvez utiliser ce qui suit:

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

3 Convert données à lister.

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

4 Utilisez Impromptu pour convertir Liste Liste <IExampleInterface>

var result = new List<IExampleInterface>(); 

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