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.