あなたのコメントによると、あなたは基本的に階層関係を実行するためのpost_id
と列を持っている現在のデータベーススキーマを改善することについての提案を受け入れています。child_post_id
それでは先に進みましょう:
SQLを使用してこのデータを取得するための最良の方法は何ですか?
このような階層データを非常に効率的に管理するための非常に優れた手法を示す次の記事を参照することをお勧めします。入れ子集合モデルを使用します。このモデルでは、左右のノードでセットを定義し、単一の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>
そしてもちろん、最終的な結果は予想されるものです。

アップデート:
コメントセクションで要求されたように、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();
}
今、あなたはやっています。この場合、プレーンな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);
}
}
最後の部分は、リポジトリの目的の実装を注入するようにお気に入りの依存性注入フレームワークを構成することです。これまでのところ、それは1つしかないためPostsRepositoryAdoNet
です。そして、明日ORMに切り替えることにした場合、あなたがしなければならないのは、IPostsRepository
インターフェースを実装する対応するリポジトリーを書くことだけです。