Groupe LINQ dynamique par plusieurs colonnes

.net c# dynamic-linq entity-framework linq

Question

J'ai besoin de traduire la requête LINQ suivante en Dynamic LINQ qui accepte plusieurs colonnes de regroupement en fonction des entrées de l'utilisateur. En gros, j'ai un tas de listes déroulantes qui appliquent des groupes et je ne veux pas énumérer toutes les combinaisons de groupes. Si Dynamic LINQ échoue, il se peut que je doive construire une requête SQL manuellement, et personne ne le souhaite.

var grouping = ( from entry in ObjectContext.OmniturePageModules
    where entry.StartOfWeek >= startDate && entry.StartOfWeek <= endDate &&
        ( section == "Total" || section == "All" || entry.Section == section ) &&
        ( page == "Total" || page == "All" || entry.Page == page ) &&
        ( module == "Total" || module == "All" || entry.Module == module ) 
    group entry by new
    {
        entry.Page, // I want to be able to tell this anonymous type
        entry.Module, // which columns to group by
        entry.StartOfWeek // at runtime
    }
    into entryGroup
    select new
    {
        SeriesName = section + ":" + entryGroup.Key.Page + ":" + entryGroup.Key.Module,
        Week = entryGroup.Key.StartOfWeek,
        Clicks = entryGroup.Sum( p => p.Clicks )
    } );

Je ne sais pas comment faire cela car LINK dynamique est totalement non documenté en dehors du "hello world!" sélectionner / où / commander par cas. Je n'arrive pas à comprendre la syntaxe.

Quelque chose comme:(?)

var grouping = ObjectContext.OmniturePageModules.Where(entry => entry.StartOfWeek >= startDate && entry.StartOfWeek <= endDate &&
                                           ( section == "Total" || section == "All" || entry.Section == section ) &&
                                           ( page == "Total" || page == "All" || entry.Page == page ) &&
                                           ( module == "Total" || module == "All" || entry.Module == module ))
                                           .GroupBy("new (StartOfWeek,Page,Module)", "it")
                                           .Select("new (Sum(Clicks) as Clicks, SeriesName = section + key.Page + Key.Module, Week = it.Key.StartOfWeek)");

J'utilise la classe DynamicQueryable dans System.Linq.Dynamic. Voir: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Suivi: La solution d’Enigmativity a fonctionné principalement . Pour une raison quelconque, il ne souhaite pas grouper en fonction de la colonne "StartOfWeek" datetime - la solution de contournement consiste simplement à créer un groupe secondaire:

var entries = ( from entry in ObjectContext.OmniturePageModules
                            where entry.StartOfWeek >= startDate
                                && entry.StartOfWeek <= endDate
                                && ( section == "Total" || section == "All" || entry.Section == section )
                                && ( page == "Total" || page == "All" || entry.Page == page )
                                && ( module == "Total" || module == "All" || entry.Module == module )
                            select entry ).ToArray(); // Force query execution

            var grouping = from entry in entries
                            let grouper = new EntryGrouper( entry, section, page, module )
                            group entry by grouper into entryGroup
                            select new
                            {
                                entryGroup.Key.SeriesName,
                                entryGroup.Key.Date, 
                                Clicks = entryGroup.Sum( p => p.Clicks ),
                            };

            var grouping2 = (from groups in grouping
                            group groups by new {groups.SeriesName, groups.Date } into entryGroup
                            select new
                            {
                               entryGroup.Key.SeriesName,
                               entryGroup.Key.Date,
                               Clicks = entryGroup.Sum( p => p.Clicks ),
                            } );

mais cela semble sérieusement dégrader les performances ... = /

Réponse acceptée

Si vous voulez explicitement utiliser la bibliothèque de requêtes dynamiques LINQ, ma réponse ne sera pas celle que vous souhaitez, mais si vous voulez le comportement souhaité et que vous préférez utiliser LINQ, je pense pouvoir vous aider.

En EntryGrouper j'ai créé une classe EntryGrouper qui gère la logique de regroupement en fonction des valeurs sélectionnées dans les listes déroulantes et j'ai supposé que la section variables, page & module contenait ces valeurs. J'ai également supposé que ObjectContext.OmniturePageModules est un énumérable de type Entry .

Donc, votre requête LINQ devient maintenant ces deux:

var entries = (from entry in ObjectContext.OmniturePageModules
               where entry.StartOfWeek >= startDate
                   && entry.StartOfWeek <= endDate
                   && (section == "Total" || section == "All" || entry.Section == section)
                   && (page == "Total" || page == "All" || entry.Page == page)
                   && (module == "Total" || module == "All" || entry.Module == module)
               select entry).ToArray(); // Force query execution

var grouping = from entry in entries
               let grouper = new EntryGrouper(entry, section, page, module)
               group entry by grouper into entryGroup
               select new
               {
                   SeriesName = entryGroup.Key.SeriesName,
                   Week = entryGroup.Key.StartOfWeek,
                   Clicks = entryGroup.Sum(p => p.Clicks),
               };

La première requête est utilisée pour forcer une requête de sélection simple sur la base de données et renvoyer uniquement les enregistrements que vous souhaitez regrouper. En règle générale group by requêtes group by appel à la base de données à plusieurs reprises; l'interrogation de cette manière est généralement beaucoup plus rapide.

La deuxième requête regroupe les résultats de la première requête en créant des instances de la classe EntryGrouper tant que clé de regroupement.

J'ai inclus une propriété SeriesName dans la classe EntryGrouper afin que toute la logique de regroupement soit clairement définie à un seul endroit.

Maintenant, la classe EntryGrouper est assez grande car, pour permettre le regroupement, elle doit avoir des propriétés pour StartOfWeek , Section , Page et Module , contenir les surcharges des méthodes Equals & GetHashCode et implémenter l' IEquatable<Entry> .

C'est ici:

public class EntryGrouper : IEquatable<Entry>
{
    private Entry _entry;
    private string _section;
    private string _page;
    private string _module;

    public EntryGrouper(Entry entry, string section, string page, string module)
    {
        _entry = entry;
        _section = section;
        _page = page;
        _module = module;
    }

    public string SeriesName
    {
        get
        {
            return String.Format("{0}:{1}:{2}", this.Section, this.Page, this.Module);
        }
    }

    public DateTime StartOfWeek
    {
        get
        {
            return _entry.StartOfWeek;
        }
    }

    public string Section
    {
        get
        {
            if (_section == "Total" || _section == "All")
                return _section;
            return _entry.Section;
        }
    }

    public string Page
    {
        get
        {
            if (_page == "Total" || _page == "All")
                return _page;
            return _entry.Page;
        }
    }

    public string Module
    {
        get
        {
            if (_module == "Total" || _module == "All")
                return _module;
            return _entry.Module;
        }
    }

    public override bool Equals(object other)
    {
        if (other is Entry)
            return this.Equals((Entry)other);
        return false;
    }

    public bool Equals(Entry other)
    {
        if (other == null)
            return false;
        if (!EqualityComparer<DateTime>.Default.Equals(this.StartOfWeek, other.StartOfWeek))
            return false;
        if (!EqualityComparer<string>.Default.Equals(this.Section, other.Section))
            return false;
        if (!EqualityComparer<string>.Default.Equals(this.Page, other.Page))
            return false;
        if (!EqualityComparer<string>.Default.Equals(this.Module, other.Module))
            return false;
        return true;
    }

    public override int GetHashCode()
    {
        var hash = 0;
        hash ^= EqualityComparer<DateTime>.Default.GetHashCode(this.StartOfWeek);
        hash ^= EqualityComparer<string>.Default.GetHashCode(this.Section);
        hash ^= EqualityComparer<string>.Default.GetHashCode(this.Page);
        hash ^= EqualityComparer<string>.Default.GetHashCode(this.Module);
        return hash;
    }

    public override string ToString()
    {
        var template = "{{ StartOfWeek = {0}, Section = {1}, Page = {2}, Module = {3} }}";
        return String.Format(template, this.StartOfWeek, this.Section, this.Page, this.Module);
    }
}

La logique de regroupement de cette classe ressemble simplement à ceci:

if (_page == "Total" || _page == "All")
    return _page;
return _entry.Page;

Si j'ai mal compris comment les valeurs de liste déroulante activent et désactivent le groupe, il vous suffira simplement de changer ces méthodes, mais le noeud de ce code est que, lorsque le groupe est activé, il doit renvoyer une valeur de groupe basée sur la valeur de l'entrée et sinon, il devrait renvoyer une valeur commune pour toutes les entrées. Si la valeur est commune à toutes les entrées, il ne crée logiquement qu'un seul groupe, ce qui revient au même que ne pas grouper du tout.

Si vous regroupez plusieurs EntryGrouper déroulantes, vous devez ajouter davantage de propriétés à la classe EntryGrouper . N'oubliez pas d'ajouter ces nouvelles propriétés aux méthodes Equals & GetHashCode .

Cette logique représente donc le regroupement dynamique souhaité. S'il vous plaît laissez-moi savoir si j'ai aidé ou si vous avez besoin de plus de détails.

Prendre plaisir!


Réponse populaire

Le voici dans Dynamic LINQ - bien sûr, vous construisez les chaînes GroupBy et Select au moment de l’exécution:

var double_grouping = ( ObjectContext.OmniturePageModules.Where( entry => entry.StartOfWeek >= startDate
                     && entry.StartOfWeek <= endDate
                     && ( section == "Total" || section == "All" || entry.Section == section )
                     && ( page == "Total" || page == "All" || entry.Page == page )
                     && ( module == "Total" || module == "All" || entry.Module == module ) )
                     .GroupBy( "new ( it.Section, it.Page, it.StartOfWeek )", "it" ) )
                     .Select( "new ( Sum(Clicks) as Clicks, Key.Section as SeriesSection, Key.Page as SeriesPage, Key.StartOfWeek as Week )" );

Et voici le chemin LINQ normal qui m’a échappé jusqu’à ce qu’un collègue l’ait signalé - c’est en gros la solution d’Enigmativity sans la classe de mérou:

var grouping = ( from entry in ObjectContext.OmniturePageModules
    where entry.StartOfWeek >= startDate && entry.StartOfWeek <= endDate &&
        ( section == "Total" || section == "All" || entry.Section == section ) &&
        ( page == "Total" || page == "All" || entry.Page == page ) &&
        ( module == "Total" || module == "All" || entry.Module == module )
    group entry by new
    {
        Section = section == "All" ? entry.Section : section,
        Page = page == "All" ? entry.Page : page,
        Module = module == "All" ? entry.Module : module,
        entry.StartOfWeek
    }
        into entryGroup
        select new
        {
            SeriesName =
            entryGroup.Key.Section + ":" + entryGroup.Key.Page + ":" + entryGroup.Key.Module,
            Week = entryGroup.Key.StartOfWeek,
            Clicks = entryGroup.Sum( p => p.Clicks )
        } );


Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow