2011-06-03 22 views
10

decir que tienen el CTE siguiente que devuelve el nivel de algunos datos de árbol (modelo de adyacencia) que he (tomado de Hierarchical data in Linq - options and performance):Simulación de recursividad CTE en C#

WITH hierarchy_cte(id, parent_id, data, lvl) AS 
(
    SELECT id, parent_id, data, 0 AS lvl 
    FROM dbo.hierarchical_table 
    WHERE (parent_id IS NULL) 

    UNION ALL 

    SELECT t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl 
    FROM dbo.hierarchical_table AS t1 
    INNER JOIN hierarchy_cte AS h ON t1.parent_id = h.id 
) 
SELECT id, parent_id, data, lvl 
FROM hierarchy_cte AS result 

Me preguntaba si habría alguna el rendimiento aumenta al hacer la recursión en C# en lugar de SQL. ¿Alguien puede mostrarme cómo realizar el mismo trabajo que hace el CTE con una función C# recursiva suponiendo que tengo un IQueryable donde Tree es una entidad que representa una entrada en la tabla jerárquica? Algo similar a:

public void RecurseTree(IQueryable<Tree> tree, Guid userId, Guid parentId, int level) 
{ 
    ... 
    currentNode.level = x 
    ... 
    Recurse(tree... ,level + 1) 
} 

Sería genial ver que esto es fácil de hacer usando una expresión lambda.

Respuesta

5

La recursividad en SQL Server es tremendamente lenta por comparación pero funciona.

Tendría que decir que T-SQL es algo limitado, pero nunca se pensó para hacer todas esas operaciones en primer lugar. No creo que haya ninguna manera de que pueda hacer esto con un IQueryable si se inundó para ejecutar esto en su contra la instancia de SQL Server, pero puede hacerlo en la memoria en la máquina que ejecuta el código utilizando LINQ-to-Objects en un forma compacta.

Aquí hay una manera de hacerlo:

class TreeNode 
{ 
    public int Id; 
    public int? ParentId; 
} 

static void Main(string[] args) 
{ 
    var list = new List<TreeNode>{ 
     new TreeNode{ Id = 1 }, 
      new TreeNode{ Id = 4, ParentId = 1 }, 
      new TreeNode{ Id = 5, ParentId = 1 }, 
      new TreeNode{ Id = 6, ParentId = 1 }, 
     new TreeNode{ Id = 2 }, 
      new TreeNode{ Id = 7, ParentId= 2 }, 
       new TreeNode{ Id = 8, ParentId= 7 }, 
     new TreeNode{ Id = 3 }, 
    }; 

    foreach (var item in Level(list, null, 0)) 
    { 
     Console.WriteLine("Id={0}, Level={1}", item.Key, item.Value); 
    } 
} 

private static IEnumerable<KeyValuePair<int,int>> Level(List<TreeNode> list, int? parentId, int lvl) 
{ 
    return list 
     .Where(x => x.ParentId == parentId) 
     .SelectMany(x => 
      new[] { new KeyValuePair<int, int>(x.Id, lvl) }.Concat(Level(list, x.Id, lvl + 1)) 
     ); 
} 
+0

funcionó como un encanto. el tiempo empleado ha bajado de 3 segundos a <1. Gracias :) – woggles

5

Las lambdas genuinamente recursivas (y por deducción, Expression s) son técnicamente posibles, pero pretty much insane. También esperaría que cualquier analizador sintáctico (L2S, EF, etc.) excepto quizás LINQ-to-Objects se enloquezca al intentar desmantelar eso.

En resumen: haría bien en considerar esto como un mecanismo no compatible para Expression.

Por último, cabe destacar que sólo porque usted está escribiendo un Expression no significa que se está ejecutando en C# - de hecho, muy probablemente lo contrario: si está escribiendo activamente un Expression (en lugar de un delegado o código de procedimiento) Supongo que va a un analizador (a menos que haya usado .AsQueryable() para enviarlo a LINQ-to-Objects).

+0

+1, excelente enlace en 'expressions' lambda recursiva! –