Linq To Object, type de sortie modulable

Résolu
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 - Modifié le 23 juin 2013 à 23:54
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 - 23 juin 2013 à 23:54
Bonjour,

j'ai un Championnat, composé d'une List<Manche>, chaque manche ayant un numéro et une List, chaque participant est quant à lui définit par son Nom et un nombre de Points.
Le nombre de manche pour le championnat n'est pas connu à l'avance.

Je dois sortir un tableau de synthèse dont les colonnes sont:
Nom, Points manche 1, Points manche 2, ..., Total des points

J'y arrive en écrivant cellule par cellule mon tableau, mais j'espérais le faire avec Linq.

Ceci fonctionne, mais nécessite de connaitre le nombre de manches (3 pour l'exemple):

            var synthese = (from m in champ.manches
                               from p in m.participants
                               group new{ Points <bold>p.Points, Nom</bold> p.Nom, Numero = m.Numero } by p.Nom into pGroupes
                               let total <bold>pGroupes.Sum(p</bold>> p.Points)
                               select new
                               {
                                   Nom = pGroupes.Key,
                                   total,
                                   Manche1 <bold>pGroupes.Where(c</bold>> c.Numero == 1).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                   Manche2 <bold>pGroupes.Where(c</bold>> c.Numero == 2).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                   Manche3 =pGroupes.Where(c => c.Numero == 3).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,

                               }
                            ).ToList();



Existe-t-il un moyen de faire comme le if(que le compilateur n'accepte pas) dans le code suivant:
            var synthese = (from m in champ.manches
                               from p in m.participants
                               group new{ Points <bold>p.Points, Nom</bold> p.Nom, Numero = m.Numero } by p.Nom into pGroupes
                               let total <bold>pGroupes.Sum(p</bold>> p.Points)
                               let nombreManches = champ.manches.Count
                               select new
                               {
                                   Nom = pGroupes.Key,
                                   total,
                                   Manche1 <bold>pGroupes.Where(c</bold>> c.Numero == 1).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                   Manche2 <bold>pGroupes.Where(c</bold>> c.Numero == 2).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                   //ici j'aimerais un truc du style, si le nombre de manche n'est pas
                                   //atteint, j'ajoute une propriété et je la remplie
                                   //Et si ça pouvait être fait dans une boucle ce serait encore mieux
                                   if (nombreManches ><bold>3) Manche3</bold>pGroupes.Where(c => c.Numero == 3).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,

                               }
                            ).ToList();

Whismeril

4 réponses

Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
Modifié par Whismeril le 8/05/2014 à 19:12
J'ai trouvé une solution en compilant du code en dynamique.
Cependant cela s'avère plus compliqué que décrire le datagridview cellule par cellule.
Je le mets quand même pour la postérité!

le but est de compiler en live cette classe:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace test_jointure
{
    class Synthese
    {
        private static void MaSynthese(Championnat champ, DataGridView dtw)
        {
            var synthese = (from m in champ.manches
                            from p in m.participants
                            group new { Points <bold>p.Points, Nom</bold> p.Nom, Numero = m.Numero } by p.Nom into pGroupes
                            let total <bold>pGroupes.Sum(p</bold>> p.Points)
                            let nombreManches = champ.manches.Count
                            select new
                            {
                                Nom = pGroupes.Key,
                                Total = total,
                                Manche1 <bold>pGroupes.Where(c</bold>> c.Numero == 1).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                Manche2 <bold>pGroupes.Where(c</bold>> c.Numero == 2).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                Manche3 <bold>pGroupes.Where(c</bold>> c.Numero == 3).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,
                                //en ajoutant ici autant de MancheN que nécessaire 

                            }
                ).ToList();

            dtw.DataSource = synthese;

        }

    }
}


Une méthode ne pouvant pas être de type var, j'ai du ecrire la méthode de sorte qu'elle affecte la liste synthese au datasource du datagridview.


Pour la génération en dynamique, j'écris d'abord ma classe dans un stringbuilder, puis j'initialise un provider et un compiler (espaces de noms Microsoft.CSharp et System.CodeDom.Compiler).

J'ajoute les références nécessaires à la compilation et enfin j'invoque la méthode:

            //ecriture du code dans un stringbuilder
            StringBuilder codeDynamique = new StringBuilder();
            //partie du code "fixe"
            codeDynamique.AppendLine("using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms;");
            codeDynamique.AppendLine("namespace test_jointure {");
            codeDynamique.AppendLine("public class Synthese {");
            codeDynamique.AppendLine("public static void Calcul(Championnat champ, DataGridView dtw)");
            codeDynamique.AppendLine("{");
            codeDynamique.AppendLine("var synthese = (from m in champ.manches");
            codeDynamique.AppendLine("from p in m.participants");
            codeDynamique.AppendLine("group new { Points <bold>p.Points, Nom</bold> p.Nom, Numero = m.Numero } by p.Nom into pGroupes");
            codeDynamique.AppendLine("let total <bold>pGroupes.Sum(p</bold>> p.Points)");
            codeDynamique.AppendLine("let nombreManches = champ.manches.Count");
            codeDynamique.AppendLine("select new");
            codeDynamique.AppendLine("{");
            codeDynamique.AppendLine("Nom = pGroupes.Key,");
            codeDynamique.AppendLine("Total =total,");
            
            //partie du code modulable, je décris un champ manche autant de fois que nécessaire
            string texteAvecAccolades <bold>"new { Points</bold> 0, Nom = string.Empty, Numero = 0 }";//pour le string.Format je trouve plus lisble de faire ainsi que d'échapper l'accolade par \\\\...
            for (int i = 0;i<champ.manches.Count;i++)
                codeDynamique.AppendLine(string.Format("Manche{0} <bold>pGroupes.Where(c</bold>> c.Numero == {0}).DefaultIfEmpty({1}).ElementAt(0).Points,", i + 1, texteAvecAccolades));

            //fin fixe
            codeDynamique.AppendLine("}).ToList();");
            codeDynamique.AppendLine("dtw.DataSource = synthese;");
            codeDynamique.AppendLine("}");
            codeDynamique.AppendLine("}}");

            string code = codeDynamique.ToString();


            //compilation dynamique
            CSharpCodeProvider provider = new CSharpCodeProvider();
            ICodeCompiler compileur = provider.CreateCompiler();


            CompilerParameters cp = new CompilerParameters();
            //Ajout des références nécessaire, j'ai pris toutes les références du projet et supprimé une par ou pour voir celles qui étaient vraiment necessaires
            cp.ReferencedAssemblies.Add(@"System.dll");
            cp.ReferencedAssemblies.Add(@"System.Core.dll");
            cp.ReferencedAssemblies.Add(@"System.Windows.Forms.dll");
            cp.ReferencedAssemblies.Add(@"test jointure.exe");//le projet en cours, car j'utilise le type Cahmpionnat
            
            //compilation
            CompilerResults cr = provider.CompileAssemblyFromSource(cp,code);
            
            
            if (cr.Errors.Count == 0)//appel de la méthode Calcul de la classe Synthese
                cr.CompiledAssembly.GetType("test_jointure.Synthese").GetMethod("Calcul").Invoke(null, new object[] { champ, dataGridView1 });


Whismeril
3
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
Modifié par Whismeril le 8/05/2014 à 19:13
J'ai trouvé une bidouille avec System.Linq.Dynamic qui ne me satisfait pas entièrement, mais qui a le mérite de fonctionner.

Comme je n'arrivais pas à transférer mes données groupées dans la clause Select, j'ai écrit une classe avec une List<T> de données.
Dans la première requête je remplis une LisT de cette classe. J'ai voulus y mettre une méthode pour aller chercher les points de la manche n, mais Dynamic.cs m'a encore dit qu'il ne pouvait y accéder.
J'ai donc écrit 20 propriétés en espérant ne jamais attendre ce nombre de manches:

    public class ResultatGroupe
    {
        public string Pseudo { get; set; }

        public int TotalPoints { get; set; }

        public List<ResulatUnitaire> Resultats { get; set; }

        public int GetPoints(int numero)//Dynamic.cs ne peut pas accéder à cette méthode.
        {
            if (Resultats.Exists(x => x.Numero == numero))
                return Resultats.First(x => x.Numero == numero).Points;
            else return 0;//il y a sans doute mieux à faire avec un DefaultIfEmpty, mais je n'ai pas cherché à optimiser cette partie
        }

        #region Résultats détaillés
//listing des résultats
        public int R1
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 1))
                    return Resultats.First(x => x.Numero == 1).Points;
                else return 0;
            }
        }

        public int R2
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 2))
                    return Resultats.First(x => x.Numero == 2).Points;
                else return 0;
            }
        }

        public int R3
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 3))
                    return Resultats.First(x => x.Numero == 3).Points;
                else return 0;
            }
        }

        public int R4
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 4))
                    return Resultats.First(x => x.Numero == 4).Points;
                else return 0;
            }
        }

        public int R5
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 5))
                    return Resultats.First(x => x.Numero == 5).Points;
                else return 0;
            }
        }

        public int R6
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 6))
                    return Resultats.First(x => x.Numero == 6).Points;
                else return 0;
            }
        }

        public int R7
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 7))
                    return Resultats.First(x => x.Numero == 7).Points;
                else return 0;
            }
        }

        public int R8
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 8))
                    return Resultats.First(x => x.Numero == 8).Points;
                else return 0;
            }
        }

        public int R9
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 9))
                    return Resultats.First(x => x.Numero == 9).Points;
                else return 0;
            }
        }

        public int R10
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 10))
                    return Resultats.First(x => x.Numero == 10).Points;
                else return 0;
            }
        }

       public int R11
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 11))
                    return Resultats.First(x => x.Numero == 11).Points;
                else return 0;
            }
        }

        public int R12
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 12))
                    return Resultats.First(x => x.Numero == 12).Points;
                else return 0;
            }
        }

        public int R13
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 13))
                    return Resultats.First(x => x.Numero == 13).Points;
                else return 0;
            }
        }

        public int R14
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 14))
                    return Resultats.First(x => x.Numero == 14).Points;
                else return 0;
            }
        }

        public int R15
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 15))
                    return Resultats.First(x => x.Numero == 15).Points;
                else return 0;
            }
        }

        public int R16
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 16))
                    return Resultats.First(x => x.Numero == 16).Points;
                else return 0;
            }
        }

        public int R17
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 17))
                    return Resultats.First(x => x.Numero == 17).Points;
                else return 0;
            }
        }

        public int R18
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 18))
                    return Resultats.First(x => x.Numero == 18).Points;
                else return 0;
            }
        }

        public int R19
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 19))
                    return Resultats.First(x => x.Numero == 19).Points;
                else return 0;
            }
        }

        public int R20
        {
            get
            {
                if (Resultats.Exists(x => x.Numero == 20))
                    return Resultats.First(x => x.Numero == 20).Points;
                else return 0;
            }
        }

        #endregion
    }

    public class ResulatUnitaire
{
        public int Numero { get; set; }

        public int Points { get; set; }
}



et là mes deux requêtes:
            List<ResultatGroupe> pGroupes = (from m in Champ.Manches
                            from p in m.Participants
                            group new { Points <bold>p.Points, Numero</bold> m.Numero } by p.Pseudo into groupe
                            select new ResultatGroupe
                            {
                                Pseudo = groupe.Key,
                                TotalPoints = groupe.Sum(x=> x.Points),
                                Resultats <bold>groupe.Select(x</bold>> new ResulatUnitaire{Numero =x.Numero, Points=x.Points}).ToList<ResulatUnitaire>()

                            }
                            ).ToList <ResultatGroupe>();
                
            //partie commune
            StringBuilder str = new StringBuilder();
            str.Append("new (");
            str.Append("Pseudo as Pseudo, TotalPoints as Total");

            //Partie dynamique
            for (int i = 0; i < Champ.Manches.Count; i++)
            {
                string ligne = string.Format(", R{0} as Manche{0}", i+1);//ici je viens chercher la propriété qui va bien
                str.Append(ligne);
            }
            str.Append(")");

            var synthese = pGroupes.AsQueryable().Select(str.ToString());//le Select doit être appliqué à un Iqueriable

            datagridview1.DataSource = synthese.Cast<object>().ToList();//synthese est donc un Iqueriable qui n'est pas un résulat mais une requête, il n'a pas de méthode ToList pour forcer l'énumération d'ou le cast en object.


Ce qui m'ennuie, c'est que je dois décrire la classe ResultatGroupe avec un maximum de manches pré-définies.

Whismeril
3
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
9 oct. 2012 à 18:21
un petit up.


Whismeril
0
Whismeril Messages postés 19026 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 20 avril 2024 656
Modifié par Whismeril le 8/05/2014 à 19:14
@Bidou,

j'ai regardé deux des pistes évoquées dans l'autre discussion.

L'opérateur ?: et let, je connaissais et les utilisais avant le select ou dedans pour ?: afin de conditionner le remplissage d'un champ.
J'ai testé l'initialisation d'un champ avec et le compilateur me dit que je ne peux pas faire ainsi:

            var pGroupes = (from m in Champ.Manches
                            from p in m.Participants
                            group new { Points <bold>p.Points, Nom</bold> p.Pseudo, Numero = m.Numero } by p.Pseudo
                            );

            var synthese <bold>pGroupes.Select(groupe</bold>> new
            {
                Nom = groupe.Key,
                Total <bold>groupe.Sum(p</bold>> p.Points),
                groupe.Any(c => c.Numero == 1) ? Manche1 = groupe.First(c => c.Numero == 1).Points : null,//deux erreurs : Erreur 2 Déclarateur de membre de type anonyme non valide. Les membres de type anonyme doivent être déclarés avec une assignation de membre, un nom simple ou un accès membre. Erreur 1 Le nom 'Manche1' n'existe pas dans le contexte actuel
                Manche2 <bold>groupe.Where(c</bold>> c.Numero == 2).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,//ça c'est ok
                Manche3 <bold>groupe.Where(c</bold>> c.Numero == 3).DefaultIfEmpty(new { Points = 0, Nom = "", Numero = 0 }).ElementAt(0).Points,//ça aussi
            }
                ).ToList();

Pour que ça fonctionne il faudra que j'écrive le code pour tester de 1 à 100 manches (admettons) et me retrouver avec 97 colonnes vide dans le datagridview si il n'y a que 3 manches, ce n'est pas le souhait du client.
A moins bien sûr que j'aie raté quelque chose.


Linq Dynamic, là aussi je cale sur l'initialisation des champs MancheX
            var pGroupes = (from m in Champ.Manches
                            from p in m.Participants
                            group new { Points <bold>p.Points, Nom</bold> p.Pseudo, Numero = m.Numero } by p.Pseudo
                            );//je laisse cette requette car je n'arrive pas à "linéariser" les from imbriqués, ça marche ce n'est pas génant

            var synthese <bold>pGroupes.AsQueryable().Select(g</bold>> new
                                                            {
                                                                Nom = g.Key,
                                                                Total <bold>g.Sum(p</bold>> p.Points),
                                                                Manche0 <bold>g.Where(c</bold>> c.Numero == 0).DefaultIfEmpty(new { Points = 0, Nom = string.Empty, Numero = 0 }).ElementAt(0).Points,
                                                                Manche1 <bold>g.Where(c</bold>> c.Numero == 1).DefaultIfEmpty(new { Points = 0, Nom = string.Empty, Numero = 0 }).ElementAt(0).Points,
                                                                Manche2 <bold>g.Where(c</bold>> c.Numero == 2).DefaultIfEmpty(new { Points = 0, Nom = string.Empty, Numero = 0 }).ElementAt(0).Points,
                                                                Manche3 <bold>g.Where(c</bold>> c.Numero == 3).DefaultIfEmpty(new { Points = 0, Nom = string.Empty, Numero = 0 }).ElementAt(0).Points,
                                                                Manche4 <bold>g.Where(c</bold>> c.Numero == 4).DefaultIfEmpty(new { Points = 0, Nom = string.Empty, Numero = 0 }).ElementAt(0).Points,
                                                            });//cette requête "en dur" dans le code fonctionne, c'est elle que je souhaite rendre dynamique en mettant le nombre de manche qui va bien



J'ai bien pris en compte que la syntaxe n'est plus en C#, ce bout de code là fonctionne

                //partie commune
            StringBuilder str = new StringBuilder();
            str.Append("new (");
            str.Append("Key as Nom, Sum(Points) as Total");
            str.Append(")");
            var synthese = pGroupes.AsQueryable().Select(str.ToString());


Je me retrouve bien avec le nom du participant et son total de points.

Maintenant, je n'arrive pas à accéder aux éléments issus du groupement.
str.Append("Key as Nom, Sum(Points) as Total, ElementAt(0).Points as Manche1");

Fait bugger Dynamic.cs de sorte qu'il génère:
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName), //avec methodName = ElementAt


str.Append("Key as Nom, Sum(Points) as Total, Where(m=> m.Numero = 1).Points as Manche1");

Nouvelle génération d'erreur
throw ParseError(errorPos, Res.UnknownPropertyOrField,
                        id, GetTypeName(type))// avec id = m


J'ai essayé d'autres combinaisons dont je ne me souviens plus.

Il y a assez peut d'exemples sur le net, si tu vois une piste je t'en serais reconnaissant.

Je n'ai pas encore regardé les arbres d'expressions.
0
Rejoignez-nous