Иерархические данные в Linq - параметры и производительность

У меня есть некоторые иерархические данные - каждая запись имеет идентификатор и (обнуляемый) идентификатор родительской записи. Я хочу получить все записи в дереве под данной записью. Это в базе данных SQL Server 2005. Я запрашиваю его с помощью LINQ to SQL в C# 3.5.

LINQ to SQL не поддерживает Общие табличные выражения напрямую. Мой выбор - собрать данные в коде с помощью нескольких запросов LINQ или создать представление базы данных, отображающее CTE.

Как вы думаете, какой вариант (или другой вариант) будет работать лучше при увеличении объемов данных? Поддерживается ли Тип HierarchyId SQL Server 2008 в Linq to SQL?

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
12
0
20 218
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

В MS SQL 2008 вы можете использовать HierarchyID напрямую, в sql2005 вам, возможно, придется реализовать их вручную. ParentID не так эффективен для больших наборов данных. Также проверьте эта статья для более подробного обсуждения темы.

Там не упоминается, можно ли использовать HierarchyID в LINQ to SQL.

Anthony 15.10.2008 02:09

его нельзя использовать в linq2sql из коробки

Dmitri Nesteruk 31.03.2009 19:56
Ответ принят как подходящий

Я бы настроил представление и связанную с ним табличную функцию на основе CTE. Я считаю, что, хотя вы можете реализовать логику на стороне приложения, это потребует отправки промежуточных данных по сети для вычислений в приложении. Используя конструктор DBML, представление преобразуется в сущность таблицы. Затем вы можете связать функцию с сущностью Table и вызвать метод, созданный в DataContext, для получения объектов типа, определенного представлением. Использование табличной функции позволяет механизму запросов учитывать ваши параметры при построении набора результатов, а не применять условие к набору результатов, определенному представлением постфактум.

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]

Чтобы использовать его, вы должны сделать что-то вроде - при условии некоторой разумной схемы именования:

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...
    }
}

Я пробовал такую ​​функцию, и, похоже, это правильный путь. И его можно вызвать из LINQ, прикрепленного к тексту данных. Кроме того, почему и представление, и функция? - они кажутся дублированием

Anthony 15.10.2008 19:44

Функция не отображает ту же схему, что и таблица. Он включает в себя уровень. Если у вас не было добавленного столбца, вы могли бы сопоставить его прямо с таблицей. Я считал, что уровень в иерархии важен.

tvanfosson 15.10.2008 20:13

Я сделал это двумя способами:

  1. Управляйте поиском каждого слоя дерева на основе пользовательского ввода. Представьте себе элемент управления в виде дерева, заполненный корневым узлом, дочерними элементами корня и внуками корня. Разворачиваются только корень и дочерние элементы (при сворачивании скрываются внуки). Когда пользователь расширяет дочерний узел, отображаются внуки корневого (которые были ранее извлечены и скрыты), и запускается извлечение всех правнуков. Повторите узор для N слоев в глубину. Этот шаблон очень хорошо работает для больших деревьев (глубины или ширины), потому что он извлекает только необходимую часть дерева.
  2. Используйте хранимую процедуру с LINQ. Используйте что-то вроде обычного табличного выражения на сервере, чтобы построить результаты в плоской таблице, или постройте XML-дерево в T-SQL. У Скотта Гатри есть отличная статья об использовании хранимых процедур в LINQ. Постройте свое дерево на основе результатов, когда они возвращаются, если они представлены в плоском формате, или используйте дерево XML, если это именно то, что вы возвращаете.

Я был очень увяз в поиске решения этой проблемы, когда ваш ответ заставил меня подумать о том, что мне не нужно тянуть целое дерево, просто тянуть детей, когда это необходимо.

ProfK 10.07.2012 16:18

Этот метод расширения потенциально может быть изменен для использования IQueryable. Я успешно использовал его в прошлом для коллекции объектов. Это может сработать для вашего сценария.

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

Вот как я это назвал:

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

Этот код является улучшенной версией кода здесь с исправленными ошибками.

Или вы можете проверить, откуда он это взял: weblogs.asp.net/okloeten/archive/2006/07/09/…

TheSoftwareJedi 15.10.2008 04:38

Я добавил указание на джедаев. Моя версия упрощена и улучшена.

JarrettV 15.10.2008 05:04

Я получил этот подход от Блог Роба Конери (проверьте Pt.6 для этого кода, также на codeplex), и мне нравится его использовать. Это может быть изменено для поддержки нескольких «подуровней».

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

Но можно ли его переделать для поддержки неограниченного количества подуровней?

Anthony 15.10.2008 12:13

Вы не собираетесь добавлять в этот запрос дюжину подкатегорий - он не особенно гибкий.

Kirk Broadhurst 10.11.2009 08:27

Проблема с получением данных со стороны клиента заключается в том, что вы никогда не можете быть уверены, насколько глубоко вам нужно зайти. Этот метод будет выполнять один обход на глубину, и его можно объединить для выполнения от 0 до указанной глубины за один обход.

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

Однако он не может делать произвольную глубину. Если вам действительно нужна произвольная глубина, вам нужно сделать это в базе данных, чтобы вы могли принять правильное решение остановиться.

Этот вариант также может оказаться полезным:

Метод расширения LINQ AsHierarchy ()
http://www.scip.be/index.php?Page=ArticlesNET18

Я использую это, и он работает очень хорошо, особенно обновленная версия, указанная внизу.

Chris Ridenour 20.02.2010 00:13

Я удивлен, что никто не упомянул альтернативный дизайн базы данных - когда иерархию необходимо выровнять с нескольких уровней и извлекать с высокой производительностью (не учитывая пространство для хранения), лучше использовать другую таблицу entity-2-entity для отслеживания иерархии вместо parent_id подход.

Это позволит не только отношения с одним родителем, но и отношения с несколькими родителями, указание уровня и различные типы отношений:

CREATE TABLE Person (
  Id INTEGER,
  Name TEXT
);

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

Пожалуйста, прочтите следующую ссылку.

http://support.microsoft.com/default.aspx?scid=kb;en-us;q248915

Мне не нравится этот метод - циклы «while» - не очень хорошая практика SQL, и если есть более декларативный способ сделать это, то ему следует отдать предпочтение. И теперь: используйте функцию на основе представления или таблицы, используя Common Table Expression, используя конструкцию WITH .. UNION ALL, как показано в других ответах здесь.

Anthony 01.04.2010 18:29

Пожалуйста, подумайте о том, чтобы вставить отрывок из решения на страницу, на которую вы ссылаетесь. Ссылки могут когда-нибудь умереть.

Ricardo Souza 15.08.2014 17:13

Другие вопросы по теме