notification c# linq pivot-table

c# - notification - Datos pivote usando LINQ



signalr sql server notification (5)

Estoy tratando de ver si puedo usar LINQ para resolver un problema que estoy teniendo. Tengo una colección de elementos que contienen un Enum (TypeCode) y un objeto User, y necesito aplanarlo para mostrarlo en una grilla. Es difícil de explicar, así que déjame mostrarte un ejemplo rápido.

La colección tiene elementos como estos:

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

Necesito que la salida sea:

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

¡Gracias a todos los que pueden ayudarme! Intenté hacer esto usando foreach, pero no puedo hacerlo de esa manera porque insertaría nuevos elementos en la colección en el foreach, lo que provocaría un error.


@ Sanjaya.Tio Me intrigó tu respuesta y creé esta adaptación que minimiza la ejecución de keySelector. (no probado)

public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>( this IEnumerable<TSource> source , Func<TSource, TKey1> key1Selector , Func<TSource, TKey2> key2Selector , Func<IEnumerable<TSource>, TValue> aggregate) { var lookup = source.ToLookup(x => new {Key1 = keySelector1(x), Key2 = keySelector2(x)}); List<TKey1> key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList(); List<TKey2> 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<TKey1, Dictionary<TKey2, TValue>> result = new Dictionary<TKey1, Dictionary<TKey2, TValue>>(); 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<TKey2, TValue>(); } var subDictionary = result[key1]; subDictionary[key2] = resultValue; } return result; }


La respuesta de Marc da una matriz dispersa que no se puede bombear directamente a Grid.
Traté de expandir el código desde el enlace proporcionado por Vasu de la siguiente manera:

public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>( this IEnumerable<TSource> source , Func<TSource, TKey1> key1Selector , Func<TSource, TKey2> key2Selector , Func<IEnumerable<TSource>, 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 => e.Z, o => o.V) } ).ToDictionary(e => e.X, o => o.Y); } internal class Employee { public string Name { get; set; } public string Department { get; set; } public string Function { get; set; } public decimal Salary { get; set; } } public void TestLinqExtenions() { var l = new List<Employee>() { 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()); }

* no puedo decir nada sobre el rendimiento sin embargo.


No digo que sea una gran manera de pivotar, pero es un pivote ...

// 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(); }


Puede usar .ToLookup de Linq para agrupar de la manera que está buscando.

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

Entonces, se trata de ponerlo en una forma que su consumidor pueda darle sentido. Por ejemplo:

//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); }

Ponlo en un método IEnumerable <> y (probablemente) devolverá una colección (filas) de colecciones (columna) de Usuario donde se pone un valor nulo en una columna que no tiene datos.


Supongo que esto es similar a la respuesta de Marc, pero lo publicaré ya que pasé un tiempo trabajando en ello. Los resultados están separados por " | " como en su ejemplo. También utiliza el tipo IGrouping<int, string> devuelto de la consulta LINQ al usar un grupo en lugar de construir un nuevo tipo anónimo. Esto es probado, código de trabajo.

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<int, List<string>> Rows = new Dictionary<int, List<string>>(); 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<string>()); } int RowIndex; foreach (IGrouping<int, string> c in Columns) { Rows[0].Add(c.Key.ToString()); RowIndex = 1; foreach (string user in c) { Rows[RowIndex].Add(user); RowIndex++; } for (int r = RowIndex; r <= Columns.Count(); r++) { Rows[r].Add(string.Empty); } } foreach (List<string> row in Rows.Values) { Console.WriteLine(row.Aggregate((current, next) => current + " | " + next)); } Console.ReadLine();

También lo probé con esta entrada:

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"} };

Que produjo los siguientes resultados que muestran que la primera columna no necesita contener la lista más larga. Puede usar OrderBy para obtener las columnas ordenadas por TypeCode si es necesario.

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