Читать иерархию sql в объект ac # - PullRequest
2 голосов
/ 08 февраля 2012

Как бы вы прочитали данные SQL, чтобы получить иерархический список единиц?

БЕЗ зависимости от решения только для SQL Server?

public class Unit {
    public Unit Parent { get; set; }
    public int Id { get; set; }
    public String Name { get; set; }
}

List<Unit> list = new List<Unit>();

while(reader.Read())
{
    // read sql data into clr object UNIT
}

Таблица имеет 3 столбца:

Id| ParentId | Name
1 | Null     | bla
2 |   1      | x
3 |   1      | y
4 |   2      | z
5 |   2      | test

UPDATE

That is the code which is taken from user marc_s:

 List<Unit> units = new List<Unit>();

            String commandText =
            @";WITH Hierarchy AS
              (
                 SELECT
                    ID,  ParentID = CAST(NULL AS INT),
                    Name, HierLevel = 1
                 FROM
                    dbo.Unit
                 WHERE
                    ParentID IS NULL

                 UNION ALL

                 SELECT
                    ht.ID, ht.ParentID, ht.Name, h1.HierLevel + 1
                 FROM
                    dbo.Unit ht
                 INNER JOIN 
                    Hierarchy h1 ON ht.ParentID = h1.ID
              )
              SELECT Id, ParentId, Name
              FROM Hierarchy
              ORDER BY HierLevel, Id";

            using(SqlConnection con = new SqlConnection(_connectionString))
            using (SqlCommand cmd = new SqlCommand(commandText, con))
            {
                con.Open();

                // use SqlDataReader to iterate over results
                using (SqlDataReader rdr = cmd.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        // get the info from the reader into the "Unit" object
                        Unit thisUnit = new Unit();

                        thisUnit.Id = Convert.ToInt32(rdr["Id"]);
                        thisUnit.UnitName = rdr["Name"].ToString();                     

                        // check if we have a parent
                        if (rdr["ParentId"] != DBNull.Value)
                        {
                            // get ParentId
                            int parentId = Convert.ToInt32(rdr["ParentId"]);

                            // find parent in list of units already loaded
                            // NOTE => not needed anymore => Unit parent = units.FirstOrDefault(u => u.Id == parentId);

                            // Instead use this method to find the parent:


                            Unit parent = FindParentUnit(units, parentId);

                            // if parent found - set this unit's parent to that object
                            if (parent != null)
                            {
                                thisUnit.Parent = parent;
                                parent.Children.Add(thisUnit);
                            }
                        }
                       else
                       {
                           units.Add(thisUnit);
                       }
                    }
                }
            }

            return units;

Вот скриншот заполненного списка

http://oi41.tinypic.com/rmpe8n.jpg

То есть данные sql из таблицы Unit:

http://oi40.tinypic.com/mt12sh.jpg

Вопрос

На самом деле заполненный список должен иметь только один объект, а не 11 (индекс 0 - 10). Да, первая Единица в Списке заполнена правильно, но Единицы Индекса 1 - 10 не должны быть в списке.

Вот как это должно выглядеть на самом деле:

0
|--1
|   |--3
|   |   |--9
|   |   |--10  
|   |--4
|--2
|   |--5
|   |--6
|--7
|--8

ОБНОВЛЕНИЕ И РЕШЕНИЕ

private static Unit FindParentUnit(List<Unit> units, int parentId)
        {
            Unit parent;
            foreach (Unit u in units)
            {
                if (u.Id == parentId){
                    return u;                                    
                }
                parent = FindParentUnit(u.Children, parentId);
                if (parent != null)
                    return parent;
            }
            return null;
        } 

Ответы [ 3 ]

2 голосов
/ 08 февраля 2012

Один из способов сделать это - использовать Object Relational Mapper, например, Entity Framework, чтобы выполнить эту работу за вас. Этот ответ на похожий вопрос EF должен направить вас в правильном направлении.

1 голос
/ 08 февраля 2012

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

Dictionary<Int32,Unit> dic = new Dictionary<Int32,Unit>();

while(reader.Read()) 
{ 
    //create the new Unit
    // if the parent is not null get the parent unit from dic
    // add the new Unit to dic
} 
1 голос
/ 08 февраля 2012

Что-то вроде этого должно сделать это: -)

// set up connection string
string connectionString = "server=.;database=test;integrated Security=SSPI;";

// define a CTE (Common Table Expression) to recursively build your hierarchical
// structure into a flat list and order it according to its "sequence" (root first)
string cteStatement =
            @";WITH Hierarchy AS
              (
                 SELECT
                    ID,  ParentID = CAST(NULL AS INT),
                    Name, HierLevel = 1
                 FROM
                    dbo.HierarchyTest   -- replace with your table name!
                 WHERE
                    ParentID IS NULL

                 UNION ALL

                 SELECT
                    ht.ID, ht.ParentID, ht.Name, h1.HierLevel + 1
                 FROM
                    dbo.HierarchyTest ht   -- replace with your table name!
                 INNER JOIN 
                    Hierarchy h1 ON ht.ParentID = h1.ID
              )
              SELECT Id, ParentId, Name
              FROM Hierarchy
              ORDER BY HierLevel, Id";

// set up list of "Unit" objects
List<Unit> units = new List<Unit>();

// create connection and command to query             
using(SqlConnection conn = new SqlConnection(connectionString))
using(SqlCommand cmd = new SqlCommand(cteStatement, conn))
{
    conn.Open();

    // use SqlDataReader to iterate over results
    using(SqlDataReader rdr = cmd.ExecuteReader())
    {
        while(rdr.Read())
        {
            // get the info from the reader into the "Unit" object
            Unit thisUnit = new Unit();

            thisUnit.Id = rdr.GetInt32(0);
            thisUnit.Name = rdr.GetString(2);
            thisUnit.Children = new List<Unit>();

            // check if we have a parent
            if(!rdr.IsDBNull(1))
            {
                // get ParentId
                int parentId = rdr.GetInt32(1);

                // find parent in list of units already loaded
                Unit parent = units.FirstOrDefault(u => u.Id == parentId);

                // if parent found - set this unit's parent to that object
                if(parent != null)
                {
                    thisUnit.Parent = parent;
                    parent.Children.Add(thisUnit);
                }
            }
            else
            {
                units.Add(thisUnit);
            }
        }
    }

    conn.Close();
}

Это работает для вас ??

CTE (Common Table Expression) рекурсивно сканирует вашу таблицу и формирует списокиерархических узлов - сортируя его по «уровню иерархии», вы можете убедиться, что все родительские узлы получены до того, как их дочерние узлы обнаружатся (чтобы код работал)

Обновление: Хорошо, так что, кажется, вы хотите поместить только узлы с без родителя в результирующий список - это нормально (но вы на самом деле не говорили, что хотите иметь это таким образом!!) - Я обновил свой код выше - пожалуйста, проверьте еще раз !!

...