Regrouper par plusieurs colonnes


968

Comment puis-je faire plusieurs colonnes GroupBy dans LINQ

Quelque chose de similaire à cela dans SQL:

SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>

Comment puis-je le convertir en LINQ:

QuantityBreakdown
(
    MaterialID int,
    ProductID int,
    Quantity float
)

INSERT INTO @QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM @Transactions
GROUP BY MaterialID, ProductID

Réponses:


1213

Utilisez un type anonyme.

Par exemple

group x by new { x.Column1, x.Column2 }

29
Si vous êtes nouveau dans le regroupement avec des types anonymes, l'utilisation du mot-clé 'new' dans cet exemple fait la magie.
Chris

8
en cas de MVC avec nHibernate obtenant une erreur pour les problèmes de DLL. Problème résolu par GroupBy (x => new {x.Column1, x.Column2}, (key, group) => new {Key1 = key.Column1, Key2 = key.Column2, Resultat = group.ToList ()});
Milan

Je pensais que dans ce cas, les nouveaux objets seraient comparés par référence, donc pas de correspondance - pas de regroupement.
HoGo

4
Les objets typés anonymes @HoGo implémentent leurs propres méthodes Equals et GetHashCode qui sont utilisées lors du regroupement des objets.
Byron Carasco

Un peu difficile à visualiser la structure des données de sortie lorsque vous êtes nouveau sur Linq. Cela crée-t-il un regroupement où le type anonyme est utilisé comme clé?
Jacques

760

Échantillon procédural

.GroupBy(x => new { x.Column1, x.Column2 })

De quel type l'objet est-il retourné?
mggSoft

5
@MGG_Soft qui serait de type anonyme
Alex

Ce code ne fonctionne pas pour moi: "Déclarateur de type anonyme non valide."
thalesfc

19
@Tom cela devrait fonctionner comme ça. Lorsque vous ignorez le nommage des champs d'un type anonyme C # suppose que vous souhaitez utiliser le nom de la propriété / du champ finalement accédé à partir de la projection. (Donc, votre exemple est équivalent à Mo0gles ')
Chris Pfohl

1
trouvé ma réponse. Je dois définir une nouvelle entité (MyViewEntity) contenant les propriétés Column1 et Column2 et le type de retour est: IEnumerable <IGrouping <MyViewEntity, MyEntity >> et le code de regroupement est: MyEntityList.GroupBy (myEntity => new MyViewEntity {Column1 = myEntity. Column1, Column2 = myEntity.Column2});
Amir Chatrbahr

469

Ok j'ai compris ceci:

var query = (from t in Transactions
             group t by new {t.MaterialID, t.ProductID}
             into grp
                    select new
                    {
                        grp.Key.MaterialID,
                        grp.Key.ProductID,
                        Quantity = grp.Sum(t => t.Quantity)
                    }).ToList();

75
+1 - Merci pour l'exemple complet. Les extraits de l'autre réponse sont trop courts et sans contexte. Vous montrez également une fonction d'agrégation (Sum dans ce cas). Très utile. Je trouve que l'utilisation d'une fonction d'agrégation (c.-à-d. MAX, MIN, SUM, etc.) côte à côte avec le regroupement est un scénario courant.
barrypicker

Ici: stackoverflow.com/questions/14189537/… , il est affiché pour un tableau de données lorsque le regroupement est basé sur une seule colonne, dont le nom est connu, mais comment peut-il être fait si des colonnes basées sur lesquelles le regroupement doit être effectué doit être généré dynamiquement?
bg

Cela est vraiment utile pour comprendre le concept de regroupement et appliquer l'agrégation dessus.
rajibdotnet

1
Excellent exemple ... juste ce que je cherchais. J'avais même besoin de l'agrégat, donc c'était la réponse parfaite même si je cherchais du lambda, j'en ai eu assez pour répondre à mes besoins.
Kevbo

153

Pour Grouper par plusieurs colonnes, essayez ceci à la place ...

GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new 
{ 
  Key1 = key.Column1,
  Key2 = key.Column2,
  Result = group.ToList() 
});

De la même manière, vous pouvez ajouter Colonne3, Colonne4, etc.


4
Cela a été très utile et devrait recevoir beaucoup plus de votes positifs! Resultcontient tous les ensembles de données liés à toutes les colonnes. Merci beaucoup!
j00hi

1
note: j'ai dû utiliser .AsEnumerable () au lieu de ToList ()
GMan

1
Génial, merci pour ça. Voici mon exemple. Notez que GetFees renvoie un IQueryable <Fee> RegistryAccountDA.GetFees (RegistryAccountId, fromDate, toDate) .GroupBy (x => new {x.AccountId, x.FeeName}, (key, group) => new {AccountId = key.AccountId , FeeName = key.FeeName, AppliedFee = group.Sum (x => x.AppliedFee) ?? 0M}). ToList ();
Craig B

Est-il possible d'obtenir d'autres colonnes de cette requête, qui n'ont pas été regroupées? S'il y a un tableau d'objets, je voudrais obtenir cet objet groupé par deux colonnes, mais obtenir toutes les propriétés de l'objet, pas seulement ces deux colonnes.
FrenkyB

30

Depuis C # 7, vous pouvez également utiliser des tuples de valeur:

group x by (x.Column1, x.Column2)

ou

.GroupBy(x => (x.Column1, x.Column2))

2
Et bien je pense qu'il vous manque un extra) à la fin. Vous ne fermez pas le ()
StuiterSlurf

Je l'ai ajouté.
Nathan Tregillus

2
.GroupBy (x => nouveau {x.Column1, x.Column2})
Zahidul Islam Jamy

19

C # 7.1 ou supérieur en utilisant Tupleset Inferred tuple element names(actuellement , il fonctionne uniquement avec linq to objectset il est pas pris en charge lorsque les arbres d'expression sont nécessaires , par exemple someIQueryable.GroupBy(...). Question Github ):

// declarative query syntax
var result = 
    from x in inMemoryTable
    group x by (x.Column1, x.Column2) into g
    select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));

// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
    .Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));

C # 3 ou supérieur en utilisant anonymous types:

// declarative query syntax
var result3 = 
    from x in table
    group x by new { x.Column1, x.Column2 } into g
    select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };

// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
    .Select(g => 
      new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });

18

Vous pouvez également utiliser un Tuple <> pour un regroupement fortement typé.

from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
    LastName = grouping.Key.Item1,
    FirstName = grouping.Key.Item2,
    MiddleName = grouping.Key.Item3,
    DayCount = grouping.Count(), 
    AmountBilled = grouping.Sum(x => x.Rate),
}

4
Remarque: la création d'un nouveau tuple n'est pas prise en charge dans Linq To Entities
foolmoron

8

Bien que cette question concerne les propriétés de regroupement par classe, si vous souhaitez regrouper par plusieurs colonnes un objet ADO (comme un DataTable), vous devez affecter vos "nouveaux" éléments aux variables:

EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
                        .Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
                    var Dups = ClientProfiles.AsParallel()
                        .GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
                        .Where(z => z.Count() > 1)
                        .Select(z => z);

1
Je n'ai pas pu faire la requête Linq "groupe c par new {c.Field <String> (" Title "), c.Field <String> (" CIF ")}", et vous m'avez fait gagner beaucoup de temps !! la requête finale était: "groupe c par new {titulo = c.Field <String> (" Title "), cif = c.Field <String> (" CIF ")}"
netadictos

4
var Results= query.GroupBy(f => new { /* add members here */  });

7
N'ajoute rien aux réponses précédentes.
Mike Fuchs

4
.GroupBy(x => x.Column1 + " " + x.Column2)

Combiné avec Linq.Enumerable.Aggregate()ce même permet de grouper par un certain nombre de dynamique de propriétés: propertyValues.Aggregate((current, next) => current + " " + next).
Kai Hartmann

3
C'est une meilleure réponse que quiconque en attribue le mérite. Il peut être problématique s'il pourrait y avoir des cas de combinaisons où la colonne1 ajoutée à la colonne2 serait la même chose pour les situations où la colonne1 diffère ("ab" "cde" correspondrait à "abc" "de"). Cela dit, c'est une excellente solution si vous ne pouvez pas utiliser un type dynamique car vous pré-construisez des lambdas après le groupe par dans des expressions distinctes.
Brandon Barkley

3
"ab" "cde" ne devrait en fait pas correspondre à "abc" "de", d'où le blanc entre les deux.
Kai Hartmann

1
qu'en est-il de "abc de" "" et "abc" "de"?
AlbertK

@AlbertK qui ne fonctionnera pas, je suppose.
Kai Hartmann



1

Une chose à noter est que vous devez envoyer un objet pour les expressions Lambda et ne pouvez pas utiliser une instance pour une classe.

Exemple:

public class Key
{
    public string Prop1 { get; set; }

    public string Prop2 { get; set; }
}

Cela compilera mais générera une clé par cycle .

var groupedCycles = cycles.GroupBy(x => new Key
{ 
  Prop1 = x.Column1, 
  Prop2 = x.Column2 
})

Si vous ne voulez pas nommer les propriétés des clés puis les récupérer, vous pouvez le faire comme ceci à la place. Ce sera GroupBycorrectement et vous donnera les propriétés clés.

var groupedCycles = cycles.GroupBy(x => new 
{ 
  Prop1 = x.Column1, 
  Prop2= x.Column2 
})

foreach (var groupedCycle in groupedCycles)
{
    var key = new Key();
    key.Prop1 = groupedCycle.Key.Prop1;
    key.Prop2 = groupedCycle.Key.Prop2;
}

0

Pour VB et anonyme / lambda :

query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.