entity framework - tabla - Cómo contar entidades asociadas sin recuperarlas en el marco de la entidad
mapeo de tablas (6)
Me he estado preguntando sobre esto por un tiempo, así que pensé que valdría la pena usar mi primer post de Stack Overflow para preguntar al respecto.
Imagina que tengo una discusión con una lista de mensajes asociada:
DiscussionCategory discussionCategory = _repository.GetDiscussionCategory(id);
discussionCategory.Discussions es una lista de entidades de Discusión que no está cargada actualmente.
Lo que quiero es poder repetir las discusiones en una categoría de discusión y decir cuántos mensajes hay en cada discusión sin obtener los datos del mensaje.
Cuando lo he intentado antes, tuve que cargar las Discusiones y los Mensajes para poder hacer algo como esto:
discussionCategory.Discussions.Attach(Model.Discussions.CreateSourceQuery().Include("Messages").AsEnumerable());
foreach(Discussion discussion in discussionCategory.Discussions)
{
int messageCount = discussion.Messages.Count;
Console.WriteLine(messageCount);
}
Esto me parece bastante ineficiente ya que estoy obteniendo potencialmente cientos de cuerpos de mensajes de la base de datos y manteniéndolos en la memoria cuando todo lo que deseo hacer es contar su número para propósitos de presentación.
He visto algunas preguntas que tocan este tema, pero no parecen abordarlo directamente.
Gracias de antemano por cualquier idea que pueda tener sobre este tema.
Actualización: algunos más códigos según lo solicitado:
public ActionResult Details(int id)
{
Project project = _repository.GetProject(id);
return View(project);
}
Luego en la vista (solo para probarlo):
Model.Discussions.Load();
var items = from d in Model.Discussions select new { Id = d.Id, Name = d.Name, MessageCount = d.Messages.Count() };
foreach (var item in items) {
//etc
Espero que eso haga que mi problema sea un poco más claro. Avíseme si necesita más detalles del código.
Fácil; solo project en un tipo POCO (o anónimo):
var q = from d in Model.Discussions
select new DiscussionPresentation
{
Subject = d.Subject,
MessageCount = d.Messages.Count(),
};
Cuando miras el SQL generado, verás que Count()
lo hace el servidor de bases de datos.
Tenga en cuenta que esto funciona tanto en EF 1 como en EF 4.
Me he encontrado con el mismo problema al tratar con múltiples mapeadores, incluidos EF y DevExpress XPO (que ni siquiera permite que una sola entidad se asigne a varias tablas). Lo que encontré como la mejor solución es básicamente utilizar las plantillas EDMX y T4 para generar vistas actualizables en SQL Server (con en lugar de desencadenantes) y así tener un control de bajo nivel sobre el sql para que pueda hacer subconsultas en select cláusula, usa todo tipo de combinaciones complejas para traer datos, etc.
No tengo una respuesta directa, pero solo puedo indicarle la siguiente comparación entre NHibernate y EF 4.0, que parece sugerir que incluso en EF 4.0 no hay soporte inmediato para obtener recuentos de una colección de entidades relacionadas sin recuperar el colección.
http://ayende.com/Blog/archive/2010/01/05/nhibernate-vs.-entity-framework-4.0.aspx
He votado arriba y he hecho su pregunta. Con suerte, alguien responderá con una solución viable.
Sé que esta es una pregunta antigua, pero parece ser un problema continuo y ninguna de las respuestas anteriores proporciona una buena forma de tratar con agregados de SQL en vistas de lista.
Asumo modelos POCO directos y Code First como en las plantillas y ejemplos. Si bien la solución SQL View es agradable desde el punto de vista de un DBA, vuelve a introducir el desafío de mantener el código y las estructuras de la base de datos en paralelo. Para consultas agregadas SQL simples, no verá mucha ganancia de velocidad de una Vista. Lo que realmente necesita evitar son múltiples (n + 1) consultas de bases de datos, como en los ejemplos anteriores. Si tiene 5000 entidades principales y está contando entidades secundarias (por ejemplo, mensajes por discusión), eso es 5001 consultas SQL.
Puede devolver todos esos conteos en una sola consulta SQL. Así es cómo.
Agregue una propiedad de marcador de posición a su modelo de clase utilizando la anotación de datos
[NotMapped]
del espacio de nombresSystem.ComponentModel.DataAnnotations.Schema
. Esto le da un lugar para almacenar los datos calculados sin agregar una columna a su base de datos o proyectar a Modelos de Vista innecesarios.... using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MyProject.Models { public class Discussion { [Key] public int ID { get; set; } ... [NotMapped] public int MessageCount { get; set; } public virtual ICollection<Message> Messages { get; set; } } }
En su Controlador, obtenga la lista de objetos principales.
var discussions = db.Discussions.ToList();
Capture los recuentos en un diccionario. Esto genera una sola consulta SQL GROUP BY con todas las ID principales y recuentos de objetos secundarios. (Suponiendo que
DiscussionID
es el FK enMessages
).var _counts = db.Messages.GroupBy(m => m.DiscussionID).ToDictionary(d => d.Key, d => d.Count());
Pasa por los objetos principales, busca el recuento del diccionario y almacena en la propiedad del marcador de posición.
foreach (var d in discussions) { d.MessageCount = (_counts.ContainsKey(d.ID)) ? _counts[d.ID] : 0; }
Devuelve tu lista de discusión.
return View(discussions);
MessageCount
referencia a la propiedadMessageCount
en la Vista.@foreach (var item in Model) { ... @item.MessageCount ... }
Sí, podrías meter ese diccionario en el ViewBag y hacer la búsqueda directamente en la vista, pero eso enturbia tu vista con un código que no necesita estar allí.
Al final, desearía que EF tuviera una manera de hacer "contar perezoso". El problema con la carga lenta y explícita es que estás cargando los objetos. Y si tiene que cargar para contar, es un posible problema de rendimiento. El conteo diferido no resolvería el problema n + 1 en las vistas de lista, pero seguro sería bueno poder llamar a @item.Messages.Count
desde la vista sin tener que preocuparse por cargar potencialmente toneladas de datos de objetos no deseados.
Espero que esto ayude.
Si está utilizando Entity Framework 4.1 o posterior, puede usar:
var discussion = _repository.GetDiscussionCategory(id);
// Count how many messages the discussion has
var messageCount = context.Entry(discussion)
.Collection(d => d.Messages)
.Query()
.Count();
Si no se trata de una sola opción, y usted se ve en la necesidad de contar un número de entidades asociadas diferentes, una vista de base de datos podría ser una opción más simple (y potencialmente más apropiada):
Crea tu vista de base de datos.
Suponiendo que desea todas las propiedades de entidad originales más el recuento de mensajes asociado:
CREATE VIEW DiscussionCategoryWithStats AS SELECT dc.*, (SELECT count(1) FROM Messages m WHERE m.DiscussionCategoryId = dc.Id) AS MessageCount FROM DiscussionCategory dc
(Si está utilizando Primeras Migraciones de Entity Framework Code, consulte esta respuesta de SO sobre cómo crear una vista).
En EF, simplemente use la vista en lugar de la entidad original:
// You''ll need to implement this! DiscussionCategoryWithStats dcs = _repository.GetDiscussionCategoryWithStats(id); int i = dcs.MessageCount; ...