Pivoter des données avec LINQ

J’essaie de voir si je peux utiliser LINQ pour résoudre un problème que je rencontre. J’ai une collection d’éléments qui contiennent un object Enum (TypeCode) et un utilisateur, et je dois l’aplatir pour l’afficher dans une grid. C’est difficile à expliquer, alors laissez-moi vous donner un exemple rapide.

La collection a des articles comme ceux-ci:

TypeCode | User --------------- 1 | Don Smith 1 | Mike Jones 1 | James Ray 2 | Tom Rizzo 2 | Alex Homes 3 | Andy Bates 

J’ai besoin que le résultat soit:

 1 | 2 | 3 Don Smith | Tom Rizzo | Andy Bates Mike Jones | Alex Homes | James Ray | | 

Merci à tous ceux qui peuvent m’aider! J’ai essayé de le faire en utilisant foreach, mais je ne peux pas le faire parce que j’insérerais de nouveaux éléments dans la collection dans foreach, ce qui provoquerait une erreur.

Je ne dis pas que c’est un excellent moyen de pivoter – mais c’est un pivot …

  // sample data var data = new[] { new { Foo = 1, Bar = "Don Smith"}, new { Foo = 1, Bar = "Mike Jones"}, new { Foo = 1, Bar = "James Ray"}, new { Foo = 2, Bar = "Tom Rizzo"}, new { Foo = 2, Bar = "Alex Homes"}, new { Foo = 3, Bar = "Andy Bates"}, }; // group into columns, and select the rows per column var grps = from d in data group d by d.Foo into grp select new { Foo = grp.Key, Bars = grp.Select(d2 => d2.Bar).ToArray() }; // find the total number of (data) rows int rows = grps.Max(grp => grp.Bars.Length); // output columns foreach (var grp in grps) { Console.Write(grp.Foo + "\t"); } Console.WriteLine(); // output data for (int i = 0; i < rows; i++) { foreach (var grp in grps) { Console.Write((i < grp.Bars.Length ? grp.Bars[i] : null) + "\t"); } Console.WriteLine(); } 

La réponse de Marc donne une masortingce creuse qui ne peut pas être directement pompée dans Grid.
J’ai essayé d’étendre le code à partir du lien fourni par Vasu comme ci-dessous:

 public static Dictionary> Pivot3( this IEnumerable source , Func key1Selector , Func key2Selector , Func, TValue> aggregate) { return source.GroupBy(key1Selector).Select( x => new { X = x.Key, Y = source.GroupBy(key2Selector).Select( z => new { Z = z.Key, V = aggregate(from item in source where key1Selector(item).Equals(x.Key) && key2Selector(item).Equals(z.Key) select item ) } ).ToDictionary(e => eZ, o => oV) } ).ToDictionary(e => eX, o => oY); } internal class Employee { public ssortingng Name { get; set; } public ssortingng Department { get; set; } public ssortingng Function { get; set; } public decimal Salary { get; set; } } public void TestLinqExtenions() { var l = new List() { new Employee() { Name = "Fons", Department = "R&D", Function = "Trainer", Salary = 2000 }, new Employee() { Name = "Jim", Department = "R&D", Function = "Trainer", Salary = 3000 }, new Employee() { Name = "Ellen", Department = "Dev", Function = "Developer", Salary = 4000 }, new Employee() { Name = "Mike", Department = "Dev", Function = "Consultant", Salary = 5000 }, new Employee() { Name = "Jack", Department = "R&D", Function = "Developer", Salary = 6000 }, new Employee() { Name = "Demy", Department = "Dev", Function = "Consultant", Salary = 2000 }}; var result5 = l.Pivot3(emp => emp.Department, emp2 => emp2.Function, lst => lst.Sum(emp => emp.Salary)); var result6 = l.Pivot3(emp => emp.Function, emp2 => emp2.Department, lst => lst.Count()); } 

* Je ne peux rien dire sur la performance cependant.

Vous pouvez utiliser .ToLookup de Linq pour grouper de la manière que vous recherchez.

 var lookup = data.ToLookup(d => d.TypeCode, d => d.User); 

Il s’agit ensuite de le mettre sous une forme que votre consommateur puisse comprendre. Par exemple:

 //Warning: untested code var enumerators = lookup.Select(g => g.GetEnumerator()).ToList(); int columns = enumerators.Count; while(columns > 0) { for(int i = 0; i < enumerators.Count; ++i) { var enumerator = enumerators[i]; if(enumator == null) continue; if(!enumerator.MoveNext()) { --columns; enumerators[i] = null; } } yield return enumerators.Select(e => (e != null) ? e.Current : null); } 

Mettez cela dans une méthode IEnumerable <> et elle retournera (probablement) une collection (rangées) de collections (colonne) d’utilisateur où une valeur null est placée dans une colonne sans données.

Je suppose que cela ressemble à la réponse de Marc, mais je vais la poster car j’ai passé un peu de temps à y travailler. Les résultats sont séparés par " | " comme dans votre exemple. Il utilise également le type IGrouping renvoyé par la requête LINQ lors de l’utilisation d’un groupe par au lieu de créer un nouveau type anonyme. Ceci est testé, code de travail.

 var Items = new[] { new { TypeCode = 1, UserName = "Don Smith"}, new { TypeCode = 1, UserName = "Mike Jones"}, new { TypeCode = 1, UserName = "James Ray"}, new { TypeCode = 2, UserName = "Tom Rizzo"}, new { TypeCode = 2, UserName = "Alex Homes"}, new { TypeCode = 3, UserName = "Andy Bates"} }; var Columns = from i in Items group i.UserName by i.TypeCode; Dictionary> Rows = new Dictionary>(); int RowCount = Columns.Max(g => g.Count()); for (int i = 0; i <= RowCount; i++) // Row 0 is the header row. { Rows.Add(i, new List()); } int RowIndex; foreach (IGrouping c in Columns) { Rows[0].Add(c.Key.ToSsortingng()); RowIndex = 1; foreach (ssortingng user in c) { Rows[RowIndex].Add(user); RowIndex++; } for (int r = RowIndex; r <= Columns.Count(); r++) { Rows[r].Add(string.Empty); } } foreach (List row in Rows.Values) { Console.WriteLine(row.Aggregate((current, next) => current + " | " + next)); } Console.ReadLine(); 

Je l’ai aussi testé avec cette entrée:

 var Items = new[] { new { TypeCode = 1, UserName = "Don Smith"}, new { TypeCode = 3, UserName = "Mike Jones"}, new { TypeCode = 3, UserName = "James Ray"}, new { TypeCode = 2, UserName = "Tom Rizzo"}, new { TypeCode = 2, UserName = "Alex Homes"}, new { TypeCode = 3, UserName = "Andy Bates"} }; 

Ce qui a produit les résultats suivants montrant que la première colonne n’a pas besoin de contenir la liste la plus longue. Vous pouvez utiliser OrderBy pour obtenir les colonnes classées par TypeCode si nécessaire.

 1 | 3 | 2 Don Smith | Mike Jones | Tom Rizzo | James Ray | Alex Homes | Andy Bates | 

@ Sanjaya.Tio J’ai été insortinggué par votre réponse et créé cette adaptation qui minimise l’exécution de keySelector. (non testé)

 public static Dictionary> Pivot3( this IEnumerable source , Func key1Selector , Func key2Selector , Func, TValue> aggregate) { var lookup = source.ToLookup(x => new {Key1 = keySelector1(x), Key2 = keySelector2(x)}); List key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList(); List key2s = lookup.Select(g => g.Key.Key2).Distinct().ToList(); var resultQuery = from key1 in key1s from key2 in key2s let lookupKey = new {Key1 = key1, Key2 = key2} let g = lookup[lookupKey] let resultValue = g.Any() ? aggregate(g) : default(TValue) select new {Key1 = key1, Key2 = key2, ResultValue = resultValue}; Dictionary> result = new Dictionary>(); foreach(var resultItem in resultQuery) { TKey1 key1 = resultItem.Key1; TKey2 key2 = resultItem.Key2; TValue resultValue = resultItem.ResultValue; if (!result.ContainsKey(key1)) { result[key1] = new Dictionary(); } var subDictionary = result[key1]; subDictionary[key2] = resultValue; } return result; }