Как эффективно загружать данные из самосвязанной таблицы

StackOverflow https://stackoverflow.com/questions/8883368

Вопрос

Рассмотрим следующие требования для создания приложения для форума

Родительский пост

- Child Post1

    - Child Post1-1
    - Child Post1-2
        - Child Post1-2-1
- Child Post2
    - Child Post

- Child Post3

Структура таблицы

tblPost -

  • Почтовый индекс
  • Дочерний почтовый индекс
  • Название
  • Размещать Контент
  • Имя пользователя

=====================

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

Вопросы

  • Каков наилучший способ восстановить эти данные с помощью SQL?

  • Есть ли лучший способ загрузить эти данные с помощью ORM?

  • Если мы пойдем по пути SQL, каков наилучший способ загрузить эти данные в класс, как показано ниже:

    public class Post {
      public int PostId {get;set;}
      public string PostTitle {get;set;}
      public string PostContent {get;set;}
      public string PostedBy {get;set;}
      public IEnumerable<Post> ChildPosts {get;set;}
    }
    
  • Как насчет отображения такого рода данных, скажем, с использованием синтаксиса razor для представления??

Это было полезно?

Решение

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

Итак, давайте продолжим:

Каков наилучший способ восстановить эти данные с помощью SQL?

Я бы порекомендовал вам взглянуть на следующая статья это иллюстрирует очень хороший метод очень эффективного управления такими иерархическими данными.Он использует The Модель вложенного набора в котором вы определяете наборы с левым и правым узлами, а затем вы можете построить все дерево с помощью одного SQL-запроса:

enter image description here

Есть ли лучший способ загрузить эти данные с помощью ORM?

Есть способы сделать это с помощью ORM, таких как NHibernate и EF, но я оставлю это на следующий раз.Вы могли бы рассмотреть возможность разделения ваших вопросов на несколько вопросов SO, поскольку тема довольно широкая.Если вы научитесь делать это с помощью plain ADO.NET вы получите гораздо лучшее представление о базовых методах, которые задействованы, так что завтра вы решите использовать такой ORM, вы уже будете знать, что искать в порядке эффективных запросов.

Как насчет отображения такого рода данных, скажем, с использованием синтаксиса razor для представления??

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

Итак, предполагая следующую модель:

public class Post
{
    public int PostId { get; set; }
    public string PostTitle { get; set; }
    public IEnumerable<Post> ChildPosts { get; set; }
}

и следующий контроллер (в котором я, очевидно, жестко запрограммировал значения, но после прочтения руководства, на которое я ссылался в начале моего поста, вы сможете построить эту модель с помощью одного SQL-запроса):

public class HomeController : Controller
{
    public ActionResult Index()
    {
        // Hardcoding the model here, but you could use the 
        // Nested Set Model technique I have linked to 
        // in order to build this model from your database
        var post = new Post
        {
            PostId = 1,
            PostTitle = "Parent Post",
            ChildPosts = new[]
            {
                new Post 
                {
                    PostId = 2,
                    PostTitle = "Child Post 1",
                    ChildPosts = new[]
                    {
                        new Post 
                        {
                            PostId = 3,
                            PostTitle = "Child Post 1-1",
                            ChildPosts = new[]
                            {
                                new Post
                                {
                                    PostId = 4,
                                    PostTitle = "Child Post 1-2-1"
                                }
                            }
                        },
                        new Post 
                        {
                            PostId = 5,
                            PostTitle = "Child Post 1-2"
                        },
                    }
                },

                new Post 
                {
                    PostId = 6,
                    PostTitle = "Child Post 2",
                    ChildPosts = new[]
                    {
                        new Post
                        {
                            PostId = 7,
                            PostTitle = "Child Post"
                        }
                    }
                },
                new Post 
                {
                    PostId = 8,
                    PostTitle = "Child Post 3"
                },
            }
        };
        return View(post);
    }
}

и тогда у вас был бы ~/Views/Home/Index.cshtml Вид:

@model Post
<ul>
    @Html.DisplayForModel()
</ul>

и, конечно же, соответствующий шаблон отображения (~/Views/Home/DisplayTemplates/Post.cshtml), который в нашем случае будет рекурсивным для отображения полного дерева:

@model Post
<li>
    @Html.DisplayFor(x => x.PostTitle)
    <ul>
        @Html.DisplayFor(x => x.ChildPosts)
    </ul>
</li>

и, конечно, конечный результат - это то, чего можно было бы ожидать:

enter image description here


Обновить:

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

CREATE TABLE posts (id int primary key, left int, right int, title nvarchar(100));

и что вы заполнили его своими постами:

INSERT INTO posts (id, left, right, title) VALUES (1, 1, 16, 'Parent Post');
INSERT INTO posts (id, left, right, title) VALUES (2, 2, 9, 'Child Post1');
INSERT INTO posts (id, left, right, title) VALUES (3, 3, 4, 'Child Post1-1');
INSERT INTO posts (id, left, right, title) VALUES (4, 5, 8, 'Child Post1-2');
INSERT INTO posts (id, left, right, title) VALUES (5, 6, 7, 'Child Post1-2-1');
INSERT INTO posts (id, left, right, title) VALUES (6, 10, 13, 'Child Post2');
INSERT INTO posts (id, left, right, title) VALUES (7, 11, 12, 'Child Post');
INSERT INTO posts (id, left, right, title) VALUES (8, 14, 15, 'Child Post3');

Теперь ты можешь их забрать.

Но на самом деле , как всегда раньше делая что-то, что вы описываете, что вы хотите сделать.Это:вы определяете контракт:

public interface IPostsRepository
{
    Post GetPost();
}

Теперь вы доберетесь до делая.В этом случае мы будем использовать plain ADO.NET для запроса базы данных и создания объекта Post.Мы будем использовать итеративный алгоритм со стеком для построения дерева, но вы также можете использовать рекурсивный алгоритм:

public class PostsRepositoryAdoNet: IPostsRepository
{
    private readonly string _connectionString;
    public PostsRepositoryAdoNet(string connectionString)
    {
        _connectionString = connectionString;
    }

    private class Scalar
    {
        public int Depth { get; set; }
        public Post Post { get; set; }
    }

    public Post GetPost()
    {
        using (var conn = new SqlConnection(_connectionString))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();
            cmd.CommandText =
            @"
                SELECT p.id, p.title, (COUNT(parent.title) - 1) AS depth
                FROM posts AS p, posts AS parent
                WHERE p.left BETWEEN parent.left AND parent.right
                GROUP BY p.title
                ORDER BY p.left;
            ";
            using (var reader = cmd.ExecuteReader())
            {
                if (!reader.Read())
                {
                    return null;
                }

                var nodes = new Stack<Post>();
                var scalar = FromDataReader(reader);
                var rootNode = scalar.Post;
                int currentDepth = 0;
                var currentNode = rootNode;
                while (reader.Read())
                {
                    var depth = reader.GetInt32(reader.GetOrdinal("depth"));
                    if (depth > currentDepth)
                    {
                        nodes.Push(currentNode);
                        currentDepth = depth;
                    }
                    else if (depth < currentDepth)
                    {
                        while (depth < currentDepth)
                        {
                            --currentDepth;
                            nodes.Pop();
                        }
                    }
                    scalar = FromDataReader(reader);
                    currentNode = scalar.Post;
                    var p = nodes.Peek();
                    if (p.ChildPosts == null)
                    {
                        p.ChildPosts = new List<Post>();
                    }
                    p.ChildPosts.Add(currentNode);
                }
                nodes.Clear();
                return rootNode;
            }
        }
    }

    private Scalar FromDataReader(DbDataReader reader)
    {
        return new Scalar
        {
            Depth = reader.GetInt32(reader.GetOrdinal("depth")),
            Post = new Post
            {
                PostId = reader.GetInt32(reader.GetOrdinal("id")),
                PostTitle = reader.GetString(reader.GetOrdinal("title"))
            }
        };
    }
}

Теперь, когда у нас есть это хранилище, мы могли бы собрать все фрагменты воедино:

public class HomeController : Controller
{
    private readonly IPostsRepository _repository;
    public HomeController(IPostsRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Index()
    {
        var post = _repository.GetPost();
        return View(post);
    }
}

и последняя часть - настроить ваш любимый фреймворк внедрения зависимостей для внедрения желаемой реализации репозитория, и поскольку у нас пока есть только один, который будет PostsRepositoryAdoNet.И если завтра вы решите перейти на ORM, все, что вам нужно сделать, это написать соответствующий репозиторий, реализующий IPostsRepository интерфейс.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top