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
Sreedhar
la source

Réponses:

1213

Utilisez un type anonyme.

Par exemple

group x by new { x.Column1, x.Column2 }
leppie
la source
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 })
Mo0gles
la source
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();
Sreedhar
la source
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.

Milan
la source
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))
Nathan Tregillus
la source
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) });
AlbertK
la source
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),
}
Jay Bienvenu
la source
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);
Chris Smith
la source
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 */  });
Arindam
la source
7
N'ajoute rien aux réponses précédentes.
Mike Fuchs
4
.GroupBy(x => x.Column1 + " " + x.Column2)
Kai Hartmann
la source
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
3

.GroupBy(x => (x.MaterialID, x.ProductID))

Allons-y
la source
3
Pensez à ajouter une explication sur la façon dont ce code résout le problème.
Rosário Pereira Fernandes
2

groupe x par nouveau {x.Col, x.Col}

John
la source
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;
}
Ogglas
la source
0

Pour VB et anonyme / lambda :

query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })
Dani
la source