【问题标题】:How to efficiently load data from a self related table如何有效地从自相关表中加载数据
【发布时间】:2012-01-16 16:52:31
【问题描述】:

考虑以下构建论坛应用程序的要求

家长帖

- Child Post1

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

- Child Post3

表结构

tblPost -

  • PostId
  • ChildPostId
  • 标题
  • 发布内容
  • 用户名

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

我可以使用递归 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;}
    }
    
  • 使用剃刀语法来显示这种数据怎么样?

【问题讨论】:

  • 您显示的 SQL 表架构是强加的还是您愿意接受改进建议?
  • @Darin Dimitrov - 我愿意接受建议。我正在考虑计算一个孩子,但随后我们将存储一个计算值。也许是 XML??

标签: asp.net-mvc-3 collections razor self-referencing-table


【解决方案1】:

根据您的评论,您愿意接受有关改进当前数据库架构的建议,其中您基本上有一个 post_id 和一个 child_post_id 列来执行层次关系。

那么让我们继续吧:

使用 SQL 检索这些数据的最佳方法是什么?

我建议您查看following article,它说明了一种非常好的技术,可以非常有效地管理此类分层数据。它使用嵌套集模型,您可以在其中定义具有左右节点的集合,然后您可以使用单个 SQL 查询构建整个树:

有没有更好的方法来使用 ORM 加载这些数据?

使用诸如 NHibernate 和 EF 之类的 ORM 可以做到这一点,但我会留到下一次。您可能会考虑将您的问题拆分为多个 SO 问题,因为该主题非常广泛。如果您学习如何使用普通的 ADO.NET 执行此操作,您将对所涉及的底层技术有更好的理解,以便明天您决定使用这样的 ORM,您将已经知道要查找什么以进行高效查询。

使用剃刀语法来显示这种数据怎么样? 一个视图??

一旦您构建了层次模型,它就非常简单。您所要做的就是为 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>

当然,最终的结果是人们所期望的:


更新:

根据 cmets 部分的要求,这里是一个如何填充 Post 模型的示例。假设您已经按照nested set model 设计了您的数据库表:

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

现在你开始了。在这种情况下,我们将使用普通的 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 接口的相应存储库。

【讨论】:

  • 一旦我实现了嵌套集模型,你有什么建议将数据加载到对象中?谢谢你的文章!!真的很好的答案!再次感谢!
  • @Perpetualcoder,您可以使用不同的算法来实现这一点。我不能说一个比另一个更好。我会向您推荐 Donald Knut 的 The Art of Computer Programming 书,以了解不同的数据结构和算法。我已经更新了我的答案并在最后发布了一个示例。
  • 我知道那本书。就像我需要加载不平衡的树一样。
  • @Perpetualcoder,你有没有设法得到一个有效的实现?您还有其他问题吗?或者您可以考虑关闭此线程?
猜你喜欢
  • 1970-01-01
  • 2019-11-25
  • 1970-01-01
  • 2020-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
相关资源
最近更新 更多