Frage

Ich habe einige hierarchischen Daten - jeder Eintrag eine ID und ein (nullable) Eltern-Eintrag ID. Ich möchte alle Einträge im Baum unter einem bestimmten Eintrag abzurufen. Dies ist in einer SQL Server 2005-Datenbank. Ich bin mit LINQ to SQL Abfragen in C # 3.5.

LINQ to SQL unterstützt nicht die Common Table Expressions direkt. Meine Entscheidungen sind die Daten in Code mit mehreren LINQ-Abfragen zu montieren, oder einen Blick auf die Datenbank zu machen, die einen CTE Oberflächen.

Welche Option (oder eine andere Option) glauben Sie, wird eine bessere Leistung, wenn Datenvolumen groß werden? Ist SQL Server 2008 hierarchyid Typ in Linq unterstützt SQL?

War es hilfreich?

Lösung

Ich möchte einen Blick und eine zugehörige tabellenbasierte Funktion basiert auf dem CTE eingestellt. Meine Argumentation dafür ist, dass, während Sie die Logik auf der Anwendungsseite implementieren, könnte dies die Zwischendaten über den Draht für die Berechnung in der Anwendung Senden verbunden wäre. Mit Hilfe der DBML Designer übersetzt der Blick in eine Tabelle Einheit. Anschließend können Sie die Funktion mit der Tabelle Einheit verbinden und die Methode auf dem Datacontext erstellt aufrufen Objekte vom Typ der Ansicht definiert abzuleiten. Mit Hilfe der tabellenbasierten Funktion ermöglicht die Abfrage-Engine Ihre Parameter zu berücksichtigen, während die Ergebnismenge konstruieren, anstatt eine Bedingung auf der Ergebnismenge durch die Ansicht nach der Tatsache definiert, angewandt wird.

CREATE TABLE [dbo].[hierarchical_table](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [parent_id] [int] NULL,
    [data] [varchar](255) NOT NULL,
 CONSTRAINT [PK_hierarchical_table] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE VIEW [dbo].[vw_recursive_view]
AS
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


CREATE FUNCTION [dbo].[fn_tree_for_parent] 
(
    @parent int
)
RETURNS 
@result TABLE 
(
    id int not null,
    parent_id int,
    data varchar(255) not null,
    lvl int not null
)
AS
BEGIN
    WITH hierarchy_cte(id, parent_id, data, lvl) AS
   (SELECT     id, parent_id, data, 0 AS lvl
        FROM         dbo.hierarchical_table
        WHERE     (id = @parent OR (parent_id IS NULL AND @parent 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)
    INSERT INTO @result
    SELECT     id, parent_id, data, lvl
    FROM         hierarchy_cte AS result
RETURN 
END

ALTER TABLE [dbo].[hierarchical_table]  WITH CHECK ADD  CONSTRAINT [FK_hierarchical_table_hierarchical_table] FOREIGN KEY([parent_id])
REFERENCES [dbo].[hierarchical_table] ([id])

ALTER TABLE [dbo].[hierarchical_table] CHECK CONSTRAINT [FK_hierarchical_table_hierarchical_table]

Um es zu verwenden, würden Sie tun so etwas wie - ein vernünftiges Namensschema der Annahme:

using (DataContext dc = new HierarchicalDataContext())
{
    HierarchicalTableEntity h = (from e in dc.HierarchicalTableEntities
                                 select e).First();
    var query = dc.FnTreeForParent( h.ID );
    foreach (HierarchicalTableViewEntity entity in query) {
        ...process the tree node...
    }
}

Andere Tipps

Die Option auch nützlich erweisen könnte:

LINQ AsHierarchy () Erweiterungsmethode
http://www.scip.be/index.php?Page=ArticlesNET18

Ich bin überrascht, niemand ein alternatives Datenbank-Design erwähnt hat - wenn Hierarchie aus mehreren Ebenen und abgerufen mit hohen Leistung abgeflacht werden muss (nicht so Speicherplatz bedenkt) es besser ist, ein andere Entität-2-Objekt-Tabelle zu verwenden Hierarchie zu verfolgen statt parent_id Ansatz.

Es erlaubt nicht nur einzelne Elternbeziehungen, sondern auch mit mehreren Elternbeziehungen, Pegelanzeigen und verschiedene Arten von Beziehungen:

CREATE TABLE Person (
  Id INTEGER,
  Name TEXT
);

CREATE TABLE PersonInPerson (
  PersonId INTEGER NOT NULL,
  InPersonId INTEGER NOT NULL,
  Level INTEGER,
  RelationKind VARCHAR(1)
);

Ich habe diese zwei Arten geschehen:

  1. Fahren Sie das Abrufen von jeder Schicht des Baumes basierend auf Benutzereingaben. Stellen Sie sich eine Baumansicht Kontrolle mit dem Wurzelknoten bevölkert, die Kinder der Wurzel, und die Enkel der Wurzel. Nur die Wurzel und die Kinder erweitern (Enkelkinder sind mit dem Zusammenbruch versteckt). Wenn der Benutzer die Enkel der Wurzel einen untergeordneten Knoten erweitert werden Anzeige (die zuvor abgerufen und versteckt wurden) und eine Wiedergewinnung von all den Urenkel gestartet wird. Wiederholen Sie das Muster für die N-Schichten tief. Dieses Muster funktioniert sehr gut für große Bäume (Tiefe oder Breite), weil es nur den Teil des Baumes ruft benötigt.
  2. Verwenden Sie eine gespeicherte Prozedur mit LINQ. Verwenden Sie so etwas wie ein gemeinsamer Tabellenausdruck auf dem Server Ihre Ergebnisse in einem flachen Tisch zu bauen, oder eine XML-Struktur in T-SQL zu bauen. Scott Guthrie hat eine große Artikel über gespeicherte Prozeduren in LINQ verwenden. Bauen Sie Ihren Baum aus den Ergebnissen, wenn sie zurückkommen, wenn in einem flachen Format, oder verwenden Sie die XML-Struktur, wenn das ist das ist, was Sie zurückkommen.

Diese Erweiterung Methode könnte möglicherweise modifiziert werden IQueryable zu verwenden. Ich habe es erfolgreich in der Vergangenheit auf einer Sammlung von Objekten verwendet. Es kann für Ihr Szenario arbeiten.

public static IEnumerable<T> ByHierarchy<T>(
 this IEnumerable<T> source, Func<T, bool> startWith, Func<T, T, bool> connectBy)
{
  if (source == null)
   throw new ArgumentNullException("source");

  if (startWith == null)
   throw new ArgumentNullException("startWith");

  if (connectBy == null)
   throw new ArgumentNullException("connectBy");

  foreach (T root in source.Where(startWith))
  {
   yield return root;
   foreach (T child in source.ByHierarchy(c => connectBy(root, c), connectBy))
   {
    yield return child;
   }
 }
}

Hier ist, wie ich es genannt:

comments.ByHierarchy(comment => comment.ParentNum == parentNum, 
 (parent, child) => child.ParentNum == parent.CommentNum && includeChildren)

Dieser Code ist eine verbesserte, fehlerbereinigte Version des Codes gefunden hier .

In MS SQL 2008 könnten Sie HierarchyID direkt in SQL2005 können Sie sie manuell haben zu implementieren. ParentID ist nicht so performant auf große Datenmengen. Überprüfen Sie auch diesem Artikel für weitere Diskussion über das Thema.

ich diesen Ansatz bekam von Rob Conery Blog (Check um Pt. 6 für diese Code, auch auf Codeplex) und ich liebe es mit. Dies könnte refashioned werden, um mehr „sub“ Ebene zu unterstützen.

var categories = from c in db.Categories
                 select new Category
                 {
                     CategoryID = c.CategoryID,
                     ParentCategoryID = c.ParentCategoryID,
                     SubCategories = new List<Category>(
                                      from sc in db.Categories
                                      where sc.ParentCategoryID == c.CategoryID
                                      select new Category {
                                        CategoryID = sc.CategoryID, 
                                        ParentProductID = sc.ParentProductID
                                        }
                                      )
                             };

Das Problem mit den Daten von der Client-Seite zu holen ist, dass man nie sicher sein kann, wie tief Sie gehen müssen. Diese Methode wird pro Tiefe ein Hin- und Rück tun und es könnte von 0 bis zu einer bestimmten Tiefe in einem Hin- und Rück tun werden union'd.

public IQueryable<Node> GetChildrenAtDepth(int NodeID, int depth)
{
  IQueryable<Node> query = db.Nodes.Where(n => n.NodeID == NodeID);
  for(int i = 0; i < depth; i++)
    query = query.SelectMany(n => n.Children);
       //use this if the Children association has not been defined
    //query = query.SelectMany(n => db.Nodes.Where(c => c.ParentID == n.NodeID));
  return query;
}

Es kann jedoch nicht tut beliebige Tiefe. Wenn Sie wirklich beliebige Tiefe benötigen, müssen Sie, dass in der Datenbank tun - so können Sie die richtige Entscheidung treffen zu stoppen

.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top