Come caricare in modo efficiente i dati da una tabella correlata
-
29-10-2019 - |
Domanda
Considera i seguenti requisiti per creare un'app per forum
Posta genitore
- Child Post1
- Child Post1-1
- Child Post1-2
- Child Post1-2-1
- Child Post2
- Child Post
- Child Post3
Struttura della tabella
tblpost -
- ID postale
- ChildPostId
- Titolo
- Pubblica contenuto
- Nome utente
=====================
Posso recuperare questo tipo di dati utilizzando un CTE ricorsivo.Non sono sicuro che questo sia l'approccio migliore.
Domande
Qual è il modo migliore per recuperare questi dati utilizzando SQL?
Esiste un modo migliore per caricare questi dati utilizzando un ORM?
Se seguiamo il percorso SQL, qual è il modo migliore per caricare questi dati in una classe come mostrato di seguito:
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;} }
Che ne dici di visualizzare questo tipo di dati, ad esempio utilizzando la sintassi del rasoio per una vista??
Soluzione
Secondo il tuo commento sei aperto a suggerimenti su come migliorare il tuo attuale schema di database in cui fondamentalmente hai un file post_id
e un child_post_id
colonne per eseguire la relazione gerarchica.
Procediamo quindi:
Qual è il modo migliore per recuperare questi dati utilizzando SQL?
Ti consiglierei di dare un'occhiata a seguente articolo che illustra una tecnica molto interessante per gestire tali dati gerarchici in modo molto efficiente.Utilizza il The Modello di insieme annidato in cui definisci insiemi con nodi sinistro e destro e poi puoi costruire l'intero albero con una singola query SQL:
Esiste un modo migliore per caricare questi dati utilizzando un ORM?
Esistono modi per farlo utilizzando un ORM come NHibernate ed EF, ma lo lascerò per la prossima volta.Potresti considerare di dividere le tue domande in più domande SO poiché l'argomento è piuttosto ampio.Se impari come farlo utilizzando il semplice ADO.NET, acquisirai una comprensione molto migliore delle tecniche sottostanti coinvolte in modo che domani deciderai di utilizzare un tale ORM saprai già cosa cercare in ordine di query efficienti.
Che ne dici di visualizzare questo tipo di dati, diciamo utilizzando la sintassi del rasoio per una vista ??
Una volta costruito il tuo modello gerarchico è estremamente semplice.Tutto quello che devi fare è definire un modello di visualizzazione personalizzato per il file Post
digitare in cui richiameresti il modello di visualizzazione per tutti i post secondari.
Quindi assumendo il seguente modello:
public class Post
{
public int PostId { get; set; }
public string PostTitle { get; set; }
public IEnumerable<Post> ChildPosts { get; set; }
}
e il seguente controller (in cui ovviamente ho codificato i valori ma dopo aver letto il tutorial a cui ho collegato all'inizio del mio post sarai in grado di costruire questo modello con una singola query 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);
}
}
e poi avresti un ~/Views/Home/Index.cshtml
visualizzazione:
@model Post
<ul>
@Html.DisplayForModel()
</ul>
e ovviamente un modello di visualizzazione corrispondente (~/Views/Home/DisplayTemplates/Post.cshtml
) che sarà ricorsivo nel nostro caso per rendere l'albero completo:
@model Post
<li>
@Html.DisplayFor(x => x.PostTitle)
<ul>
@Html.DisplayFor(x => x.ChildPosts)
</ul>
</li>
e ovviamente il risultato finale è quello che ci si potrebbe aspettare:
AGGIORNAMENTO:
Come richiesto nella sezione commenti, ecco un esempio di come si potrebbe popolare il modello Post.Supponiamo che tu abbia seguito il modello di insieme annidato per progettare la tabella del database:
CREATE TABLE posts (id int primary key, left int, right int, title nvarchar(100));
e che lo hai riempito con i post:
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');
Ora potresti andarli a prendere.
Ma come sempre prima in realtà facendo qualcosa in cui descrivi cosa vuoi fare.Questo è:definisci un contratto:
public interface IPostsRepository
{
Post GetPost();
}
Ora arrivi al facendo.In questo caso utilizzeremo ADO.NET semplice per interrogare il database e creare l'oggetto Post.Utilizzeremo un algoritmo iterativo con uno stack per costruire l'albero ma potresti anche utilizzare un algoritmo ricorsivo:
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"))
}
};
}
}
Ora che abbiamo questo repository potremmo mettere insieme i pezzi:
public class HomeController : Controller
{
private readonly IPostsRepository _repository;
public HomeController(IPostsRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
var post = _repository.GetPost();
return View(post);
}
}
e l'ultima parte è configurare il tuo framework Dependency Injection preferito per iniettare l'implementazione desiderata del repository e poiché finora ne abbiamo solo uno sarebbe PostsRepositoryAdoNet
.E se domani decidessi di passare ad un ORM non dovrai fare altro che scrivere il repository corrispondente implementando il file IPostsRepository
interfaccia.