Согласно вашему комментарию вы открыты для предложений по улучшению вашей текущей схемы базы данных, в которой у вас есть столбцы post_id
и child_post_id
для выполнения иерархических отношений.
Итак, продолжим:
Каков наилучший способ получить эти данные с помощью SQL?
Я бы порекомендовал вам взглянуть на следующую статью , которая иллюстрирует очень хороший метод очень эффективного управления такими иерархическими данными. Он использует модель Nested Set Model , в которой вы определяете множества с левым и правым узлами, а затем вы можете построить все дерево с помощью одного SQL-запроса:
Есть ли лучший способ загрузить эти данные с помощью ORM?
Есть способы сделать это, используя ORM, такие как NHibernate и EF, но я оставлю это в следующий раз. Вы можете подумать о том, чтобы разделить ваши вопросы на несколько вопросов 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>
и, конечно, конечный результат - это то, чего можно ожидать:
UPDATE:
В соответствии с запросом в разделе комментариев приведен один пример того, как можно заполнить модель 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);
}
}
и последняя часть состоит в том, чтобы настроить вашу любимую платформу Dependency Injection для внедрения желаемой реализации репозитория, и, поскольку у нас пока есть только одна, которая будет PostsRepositoryAdoNet
. И если завтра вы решите переключиться на ORM, все, что вам нужно сделать, это написать соответствующий репозиторий, реализующий интерфейс IPostsRepository
.