type mapped herencia framework first column code c# linq entity-framework hierarchy

c# - mapped - LINQ ordena una lista plana basada en childorder



order entity framework (5)

Aquí hay una solución simple:

public class TempTable { public int ID {get;set;} public int? ParentID {get;set;} public String Name {get;set;} public int SortOrder {get;set;} } public List<TempTable> GetTempData() { var temp = new List<TempTable>(); temp.Add(new TempTable { ID = 1, ParentID = null, Name = "Test 1", SortOrder = 1 }); temp.Add(new TempTable { ID = 2, ParentID = 1, Name = "Test 2", SortOrder = 1 }); temp.Add(new TempTable { ID = 3, ParentID = 1, Name = "Test 3", SortOrder = 3 }); temp.Add(new TempTable { ID = 4, ParentID = 2, Name = "Test 4", SortOrder = 1 }); temp.Add(new TempTable { ID = 5, ParentID = 1, Name = "Test 5", SortOrder = 2 }); return temp; }

Uso:

var data = GetTempData(); var result = data.OrderBy(d => d.SortOrder).ThenBy(d => d.ParentID); //Do something with result

Actualmente estoy tratando de encontrar una buena manera de ordenar mis elementos con LINQ y C #, pero estoy fracasando en hacerlo.

Para el problema, supongamos que tiene la siguiente tabla

---TempTable ID (int) ParentID (int) Name (varchar) SortOrder (int)

ID y ParentID están relacionados entre sí y me dan una estructura de datos autoquímica. Los elementos raíz tienen un nulo en el campo ID. SortOrder es solo una parte de la tabla completa y está basado en ParentID, por lo que los elementos que comparten el mismo ParentID tienen 1, 2, 3 en él.

Supongamos además los siguientes datos:

ID = 1 ParentID = null Name = Test 1 SortOrder = 1 ID = 2 ParentID = 1 Name = Test 2 SortOrder = 1 ID = 3 ParentID = 1 Name = Test 3 SortOrder = 2 ID = 4 ParentID = 2 Name = Test 4 SortOrder = 1

Mi lista plana deseada debe tener el siguiente orden:

Test 1 //root element with sort order 1 = very top Test 2 //child element of root with sort order 1 Test 4 //child element of test 2 with sort order 1 Test 3 //child element of root with sort order 2

También me gusta obtener el objeto en sí mismo sin obtener solo una parte de la información, lanzar el uso de seleccionar nuevas ...

Este es uno de mis intentos fallidos:

from x in EntityModel.TempTables //DbSet<TempTable> by EntityFramework - which already holds all elements orderby x.SortOrder from y in x.TempTableChildren //Navigation Property by EntityFramework orderby y.SortOrder select y

Gracias de antemano por tu ayuda.

Editar:

El orden con ParentID puede ser útil, dado el TestData dado que el ID, los ParentID están en perfecto orden, pero este no es el caso en una aplicación real, ya que su información puede ser eliminada, puede eliminar una entrada, crear una nueva y colocarla en un cierto orden bajo un padre y usted tendría algo como:

ID = 193475037 ParentID = 2 Name = Test 192375937 SortOrder = 25

Ahora en la aplicación sería posible mover este y ParentID y SortOrder cambiarían aleatoriamente a algo como:

ID = 193475037 ParentID = 456798424 Name = Test 192375937 SortOrder = 4

Para explicar mejor el problema, aquí hay un código: cómo lo haría sin 1 consulta Linq hermosa, pero con 2 y retorno de rendimiento:

public class LinqTestDemo { Random rand = new Random(); List<TempTable> list = new List<TempTable>(); public List<TempTable> GetFlatData() { list = GetTestData(); var rootElement = (from x in list where x.ParentID == null orderby x.SortOrder select x).ToList(); var flatList = OrderChilds(rootElement).ToList(); foreach (var tempTable in flatList) { Console.WriteLine(string.Format("ID = {0} - ParentID = {1} - Name = {2} - SortOrder = {3}", tempTable.ID, tempTable.ParentID, tempTable.Name, tempTable.SortOrder)); } return flatList; } private IEnumerable<TempTable> OrderChilds(List<TempTable> enumerable) { foreach (var tempTable in enumerable) { yield return tempTable; TempTable table = tempTable; var childs = OrderChilds((from x in list where x.ParentID == table.ID orderby x.SortOrder select x).ToList()); foreach (var child in childs) { yield return child; } } } public List<TempTable> GetTestData() { var returnValue = new List<TempTable>(); for (int i = 0; i < 50; i++) { var tempTable = new TempTable(); tempTable.ID = i; if (i == 0) tempTable.ParentID = null; else tempTable.ParentID = rand.Next(0, i); var maxSortOrder = (from x in returnValue where x.ParentID == tempTable.ParentID select (int?)x.SortOrder).Max(); if (maxSortOrder.HasValue) tempTable.SortOrder = maxSortOrder.Value + 1; else tempTable.SortOrder = 1; tempTable.Name = string.Format("Test {0:00}", i); returnValue.Add(tempTable); } return returnValue; } public class TempTable { public int ID { get; set; } public int? ParentID { get; set; } public string Name { get; set; } public int SortOrder { get; set; } } }

@ Breadth-First vs Depth-First Traversal: Después de leer, diría que mi resultado deseado sería Depth-First Traversal, donde los elementos en la misma profundidad de nivel deberían ser ordenados por la propiedad SortOrder.


Aquí hay una versión no recursiva. No repetirá una y otra vez sobre la lista inicial. En su lugar, mantiene un diccionario para la relación padre-hijo y almacena la posición actual del recorrido en árbol de pre-orden en curso en los enumeradores.

public static IEnumerable<TempTable> PreorderForest(IEnumerable<TempTable> list) { var nodesByParent = list.GroupBy(x => x.ParentID.GetValueOrDefault(-1)) .ToDictionary(xs => xs.Key, xs => xs.OrderBy(x => x.SortOrder).GetEnumerator()); var stack = new Stack<IEnumerator<TempTable>>(); stack.Push(nodesByParent[-1]); while (stack.Count > 0) { var nodes = stack.Peek(); if (nodes.MoveNext()) { yield return nodes.Current; IEnumerator<TempTable> children; if (nodesByParent.TryGetValue(nodes.Current.ID, out children)) stack.Push(children); } else stack.Pop(); } }


En realidad, no sé si podría hacerse mediante una elegante consulta LINQ. Aquí está la versión recursiva de DFS, construye la búsqueda para acelerar la búsqueda por ParentID

public static IEnumerable<TempTable> SortedList(IEnumerable<TempTable> list = null, int? ParentID = null, ILookup<int?, TempTable> lookup = null) { if (lookup == null) lookup = list.ToLookup(x => x.ParentID, x => x); foreach (var p in lookup[ParentID].OrderBy(x => x.SortOrder)) { yield return p; foreach (var c in SortedList(lookup: lookup, ParentID: p.ID)) yield return c; } }


Prueba esto:

public class Item { public int ID { get; set; } public int? ParentID { get; set; } public string Name { get; set; } public int SortOrder { get; set; } } public void DoWork() { Item[] data = new Item[] { new Item() { ID = 2, ParentID = 1, Name = "Test 2", SortOrder = 1}, new Item() { ID = 3, ParentID = 1, Name = "Test 3", SortOrder = 2}, new Item() { ID = 4, ParentID = 2, Name = "Test 4", SortOrder = 1}, new Item() { ID = 1, ParentID = null, Name = "Test 1", SortOrder = 1}, }; var result = from x in data orderby x.SortOrder, x.ParentID select x; foreach (var row in result.ToArray()) { Console.WriteLine(row.Name); } }

Supongo que se trata de la ordenación adecuada


public lEnumerable<TempTable> GetList( int? parentID = null){ foreach ( var item in Context.TempTables .Where( x => x.ParentID == parentID ) .OrderBy( x=> x.SortOrder) .ToList() { yield return item; foreach( var child in GetList( item.ID)) { yield return child; } } } var sortedList = GetList();

Es similar a su método, pero es más pequeño y recursivo. Y funciona para muchos niveles de profundidad. Prefiero llamar a ToList porque cerrará resultados antes de consultar la próxima consulta.

No hay forma de hacer esto en una sola consulta a partir de ahora.

Con la consulta única solicitada

Entity Framework llenará automáticamente a todos los niños.

public IEnumerable<TempTable> PrepareList(IEnumerable<TempTable> list){ list = list.OrderBy( x=> x.SortOrder); foreach(var item in list){ yield return item; foreach(var child in PrepareList(item.ChildTempTables)){ yield return child; } } } // since EF will automatically fill each children on fetch // all we need is just a top level nodes // which we will pass to PrepareList method var list = Context.TempTables.ToList().Where(x=> x.ParentID == null); var sortedList = PrepareList(list).ToList(); // it is good to create list at the end if you are going to // iterate it many times and logic will not change.