Système.LINQ.Dynamique: sélectionner ("new ( ... )") dans une liste (ou toute autre collection énumérable de)

dire que j'ai un DataTable avec 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 veux utiliser le système.LINQ.Dynamique pour construire une requête dynamique qui se Groupe sur L'entreprise, le fonds, ou L'État, puis sélectionne mon groupe par critères comme 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 requête ci-dessus, groupbyvalue (Group) sélectionné sera toujours une chaîne de caractères, et la somme sera toujours double, donc je veux être capable de lancer quelque chose comme une liste, où résultat est un objet avec des propriétés Group (string) et TotalValue (double).

j'ai beaucoup de problèmes avec ça, quelqu'un peut-il nous éclairer?

20
demandé sur Shaul Behr 2009-09-23 16:31:46

4 réponses

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

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

qui devrait faire fonctionner votre requête. La question la 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: utiliser la réflexion pour accéder aux propriétés Group et TotalValue de l'objet dynamique.

Option 2: Utiliser des arbres d'expression compilés pour la génération de code léger pour accéder aux propriétés Group et TotalValue .

Option 3: Modifier la Bibliothèque dynamique pour supporter un résultat fortement dactylographié. Cela s'avère être assez simple:

  1. In ExpressionParser.Parse() , capturez l'argument type dans un champ 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 essayez d'utiliser newResultType avant de 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 dactylographié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 à l'original Select() sont les endroits auxquels nous faisons référence TResult .

maintenant nous avons juste besoin d'un type nommé pour retourner:

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

et votre requête mise à jour

    IQueryable<Result> res = table1.AsQueryable()
        .GroupBy(groupbyvalue, "it")
        .Select<Result>("new (Key as Group, Sum(Value) as TotalValue)");
41
répondu dahlbyk 2009-09-23 20:33:32

j'ai moulé les données retournées à la liste de la manière suivante:

1 étendre la méthode Select de dynamic linq pour prendre en charge les types génériques. Voir 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 ce qui suit:

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

3 Convertissez les données en liste.

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

4 Utiliser Impromptu pour convertir la liste en liste

var result = new List<IExampleInterface>();

foreach (var d in data)
{
  dynamic r = Impromptu.ActLike<IExampleInterface>(d);
  result.Add(r);
}
3
répondu Roman O 2017-05-23 12:18:17

une autre possibilité est d'étendre le langage de requête de DLinq avec la possibilité de spécifier le nom de type dans la clause new dans la chaîne de requête.

, j'ai décrit les changements nécessaires dans Dynamique.cs dans un autre stackoverflow réponse .

il vous permettra 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>;
1
répondu Krizz 2017-05-23 12:10:28

une autre façon, plus facile, de faire cela est d'utiliser JSON serializing/deserializing en utilisant le Newtonsoft.Json comme ceci:

ont 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-serialize puis deserialize au type de données que vous voulez:

string jsonData = JsonConvert.SerializeObject(q1);
List<MyClass> typedResult = JsonConvert.DeserializeObject<List<MyClass>>(jsonData);
0
répondu FyCyka LK 2018-03-20 15:55:48