web-dev-qa-db-fra.com

System.LINQ.Dynamic: Sélectionnez ("new (...)") dans une liste <T> (ou toute autre collection énumérable de <T>)

Disons que j'ai un DataTable à quatre colonnes, Société (chaîne), Fonds (chaîne), État (chaîne), Valeur (double):

    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 souhaite utiliser System.LINQ.Dynamic pour créer une requête dynamique qui regroupe des sociétés, des fonds ou des États, puis sélectionne mon groupe par critère dans la première colonne et sum (valeur):

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

Dans la requête ci-dessus, groupbyvalue (Group) sélectionné sera toujours une chaîne et la somme sera toujours un double. Je souhaite donc pouvoir effectuer un transtypage dans une liste, où Résultat est un objet avec les propriétés Groupe (chaîne ) et TotalValue (double).

Cela me pose beaucoup de problèmes. Quelqu'un peut-il nous éclairer?

20
Brad

Tout d'abord, vous accéderez à la valeur groupée actuelle sous la forme Key dans votre clause Select:

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

Cela devrait faire fonctionner votre requête. La question plus difficile est de savoir comment transformer les objets retournés, qui auront un type généré dynamiquement qui hérite de DynamicClass, en 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 des arbres d'expression compilés pour la génération de code léger 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 s'avère être assez simple:

  1. Dans ExpressionParser.Parse(), capturez l'argument de type dans un champ privé:

    private Type newResultType;
    public Expression Parse(Type resultType)
    {
        newResultType = resultType;
        int exprPos = token.pos;
        // ...
    
  2. Vers la fin de ExpressionParser.ParseNew(), nous essaierons d'utiliser newResultType avant de passer par 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 seuls changements par rapport à Select() d'origine sont des endroits auxquels nous faisons référence TResult.

Maintenant, nous avons juste besoin d'un type nommé pour renvoyer:

    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)");
42
dahlbyk

J'ai converti les données renvoyées dans List <IExampleInterface> de la manière suivante:

1 Méthode Extend Select de linq dynamique pour prendre en charge les types génériques. Voir la première réponse ici

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 les éléments suivants:

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

3 Convertir les données en liste.

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

4 Utilisez Impromptu pour convertir une liste en liste <IExampleInterface>.

var result = new List<IExampleInterface>();

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

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 modifications nécessaires dans Dynamic.cs dans un autre stackoverflow answer .

Cela vous permettra de poser des requêtes comme suit:

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

Un autre moyen, plus simple, consiste à utiliser la sérialisation/désérialisation Json à l’aide de Newtonsoft.Json comme ceci:

Avoir une classe avec les propriétés recherchées:

public class MyClass
{
    public string Group;

    public double Total;
}

Après votre requête, vous Json-sérialiser puis désérialiser sur le type de données que vous voulez:

string jsonData = JsonConvert.SerializeObject(q1);
List<MyClass> typedResult = JsonConvert.DeserializeObject<List<MyClass>>(jsonData);
0
FyCyka LK