Иерархические данные. В поиске оптимального решения

в 13:43, , рубрики: Программирование, рекурсия, метки:

Исследуя обширные пространства интернета мне пришлось потрудиться над тем как же все таки создать не тяжеловесный код на asp.net c# для обработки так называемых «свинных ушей», которые содержат классическую модель родитель-потомок в базе данных. Сразу оговорюсь база в SQL Server 2008, который уже имеет возможность работы с иерархическими данными в ОТВ(CTE — английский вариант аббревиатуры).

В моей системе строится модель бизнес-процессов, которая должна удовлетворять стандарту SADT и таблица выглядит как приведено ниже на рисунке 1.

Иерархические данные. В поиске оптимального решения
Рисунок 1. Модель

Здесь BPNumber — число которое характеризует порядок бизнес-процесса на своем уровне иерархии. К примеру сначала идет подсистема УУ(«Учебное управление») -1, «Кафедра» — 2, а затем «Деканат» — 3(рис. 2).

К примеру, результат должен выглядеть приблизительно так:
Иерархические данные. В поиске оптимального решения
Рисунок 2. Результат

Рассмотрев примеры реализации, описанные другими авторами, а так же справки с msdn автор пришла к выводу, что вытаскивать все одним списком в ОТВ и обрабатывать потом средствами asp.net лишняя загрузка сервера на построение запроса с рекурсией, да и обойти ее выстроив xml для загрузки в дерево все равно надо будет писать многочисленный код. Не оптимально подумала я.
Решив, что средствами t-sql не коротко, продолжила поиск уже в других направлениях. И нашла вот эту статью работа с иерархическими данными в asp.net mvc. Может действительно кому-то поможет?

А вот решение найденное здесь Display Hierarchical Data with TreeView in ASP.NET 2.0 поразило простотой и взяв его за основу я решила поставленную выше задачу для отображения такого как надо списка. Для преобразования значения NULL в -1 следует изменить запрос следующим образом:

SqlCommand dbCommand = new SqlCommand("SELECT  [BusinessProcessID], [BPName], [ProjectID], case when [BusinessProcessIDTOP] IS NULL  then -1 else [BusinessProcessIDTOP] end as [BusinessProcessIDTOP] FROM [BusinessProcess] WHERE ([ProjectID] = " + HiddenField1.Value.ToString() + ") order by BPNumber", myConn);

Листинг 1. Поиск верхнего узла
Ну а весь листинг выглядит вот таким образом

using System.Data.SqlClient;


public partial class bptree : System.Web.UI.Page
{
    DataTable dtTree = new DataTable();
    protected void Page_Load(object sender, EventArgs e)
    {
       
        String strConnect = SqlDataSource1.ConnectionString;
        SqlConnection myConn = new SqlConnection(strConnect);
        myConn.Open();
        SqlCommand dbCommand = new SqlCommand("SELECT  [BusinessProcessID], [BPName], [ProjectID], case when [BusinessProcessIDTOP] IS NULL  then -1 else [BusinessProcessIDTOP] end as [BusinessProcessIDTOP] FROM [BusinessProcess] WHERE ([ProjectID] = " + HiddenField1.Value.ToString() + ") order by BPNumber", myConn);
        SqlDataAdapter da = new SqlDataAdapter(dbCommand);
        da.Fill(dtTree);
        da.Dispose();
        dbCommand.Dispose();
        myConn.Dispose();
        if (!IsPostBack)
        AddNodes(-1, TreeView1.Nodes);
       
    }
    void AddNodes(int id, TreeNodeCollection tn)
    {
        foreach (DataRow dr in dtTree.Select("BusinessProcessIDTOP = " + id))
        {
            TreeNode sub = new TreeNode(dr["BPName"].ToString(), dr["BusinessProcessID"].ToString());
            tn.Add(sub);
            AddNodes(Convert.ToInt32(sub.Value), sub.ChildNodes);
        }
    }

}

Листинг 2. Решение задачи в 2 методах

Ну а что же касается ОТВ. То для описанных ниже задач думаю они подходят лучше:

  • Поиск заданного уровня узла
  • Вычисления пути(к примеру с файлами)
  • Нахождения узлов, имеющих необходимых предков
  • Ну и массовые вычисления, которые определены во множестве

К примеру приведу код t-sql с ОТВ:

/****** Сценарий для ОТВ  ******/
with cte_bp([BusinessProcessID]
      ,[BPName],[BusinessProcessIDTOP], level ,paths)
      as
(SELECT  [BusinessProcessID]
      ,[BPName]
      ,[BusinessProcessIDTOP], 0 as level, CAST([BPName]as nvarchar(max))+'/'
      
  FROM [cmk].[dbo].[BusinessProcess]
  where [ProjectID]=1 and BusinessProcessIDTOP is null
  
UNION ALL
SELECT  [BusinessProcess].[BusinessProcessID]
      ,[BusinessProcess].[BPName]
      ,[BusinessProcess].[BusinessProcessIDTOP], level+1, d.paths +cast([BusinessProcess].[BPName] as nvarchar(max))+'/'
      
  FROM [cmk].[dbo].[BusinessProcess]
  Inner join cte_bp as d on d.BusinessProcessID=BusinessProcess.BusinessProcessIDTOP
 )
-- Statement using the CTE
SELECT *, SPACE(level)+BPName as ch
FROM cte_bp order by paths,BusinessProcessID

Листинг 3. Работа с ОТВ

Вот возвращенный результат:

Иерархические данные. В поиске оптимального решения
Рисунок 3. Результат запроса

Думаю приведенный пример поможет тем, кто как и автор этого топика ищет тривиальное и быстрое решение для отображения иерархических данных, хранящихся на SQL Server 2008 и выше.

Автор: Aisu

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js