2010-01-06 11 views
39

Me he estado preguntando acerca de esto por un tiempo, así que pensé que valdría la pena usar mi primer post de Stack Overflow para preguntar al respecto.Cómo contar entidades asociadas sin recuperarlas en Entity Framework

Imagínese que tengo una discusión con una lista asociada de mensajes:

DiscussionCategory discussionCategory = _repository.GetDiscussionCategory(id); 

discussionCategory.Discussions es una lista de las entidades de discusión que no está cargado en ese momento.

Lo que quiero es poder repetir las discusiones en un discussionCategory y decir cuántos mensajes hay en cada discusión sin obtener los datos del mensaje.

cuando he intentado esto antes he tenido que cargar las discusiones y los mensajes para que yo pudiera 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 parece bastante ineficiente a mí como yo estoy retracto potencialmente cientos de mensajes cuerpos de la base de datos y mantenerlos en la memoria cuando todo lo que deseo hacer es contar su número con fines de presentación.

He visto algunas preguntas que tocan sobre este tema, pero no parecen abordarlo directamente.

Gracias de antemano por cualquier idea que pueda tener sobre este tema.

Actualización - Un poco más de código como solicitada:

public ActionResult Details(int id) 
    { 
     Project project = _repository.GetProject(id); 
     return View(project); 
    } 

A continuación, en la vista (sólo 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 hace que mi problema un poco más claro. Avíseme si necesita más detalles del código.

Respuesta

35

Fácil; simplemente project en un POCO (o anónimo) Tipo:

var q = from d in Model.Discussions 
     select new DiscussionPresentation 
     { 
      Subject = d.Subject, 
      MessageCount = d.Messages.Count(), 
     }; 

Cuando nos fijamos en el SQL generado, verá que el Count() se lleva a cabo por el servidor DB.

Tenga en cuenta que esto funciona tanto en EF y EF 1 4.

+0

Hola, gracias por la respuesta. Desafortunadamente lo he intentado antes y descubrí que la propiedad de recuento del tipo anónimo devolvió cero en todas las discusiones. Le di otro intento a este método después de ver tu respuesta, pero con el mismo resultado. Tal vez estoy malinterpretando algo sobre el marco en cuanto a que se debe tener en cuenta que las entidades estén "conectadas" al almacén de datos. ¿Alguien más puede confirmar que el método anterior debería funcionar? – Oligarchia

+0

Muestra tu código; estas haciendo algo mal. Usamos esta característica * ampliamente * en nuestras aplicaciones de envío. Sospecho que estás tratando de hacer esto en una propiedad de la asociación EF (como en tu pregunta) en lugar de en una consulta L2E (como en mi respuesta). Esos son completamente diferentes; el primero es LINQ to Objects; el último es LINQ to Entities. –

+0

El código que utilicé fue idéntico en forma al que proporcionó. Tal vez hay algo más arriba en la cadena que sale mal. Veré si puedo editar mi publicación original para proporcionar código adicional si cree que eso ayudará. Gracias de nuevo por tu ayuda. Estoy echando un vistazo a la publicación de blog a la que se vinculó en este momento. – Oligarchia

0

No tengo una respuesta directa, pero solo puedo indicarle la siguiente comparación entre NHibernate y EF 4.0, lo que parece sugerir que incluso en EF 4.0 no hay soporte inmediato para obtener recuentos de una entidad relacionada colección sin recuperar la colección.

http://ayende.com/Blog/archive/2010/01/05/nhibernate-vs.-entity-framework-4.0.aspx

He upvoted y protagonizada por su pregunta. Con suerte, alguien responderá con una solución viable.

+0

Eso es incorrecto para cada versión de EF. Ver mi respuesta –

+0

Eso está bien. Como dije, no tengo una respuesta directa a su pregunta, pero hay muchas cosas en la web que dicen que los recuentos son difíciles o imposibles. No tiendo a creer a alguien cuando dicen que algo es imposible, pero quería presentárselo al iniciador y dar comienzo a la discusión. –

10

Si está utilizando Entity Framework 4.1 o posterior, puede utilizar:

var discussion = _repository.GetDiscussionCategory(id); 

// Count how many messages the discussion has 
var messageCount = context.Entry(discussion) 
         .Collection(d => d.Messages) 
         .Query() 
         .Count(); 

Fuente: http://msdn.microsoft.com/en-US/data/jj574232

0

me he encontrado con el mismo problema cuando se trata de múltiples creadores de mapas incluyendo EF y DevExpress XPO (que no permiten incluso una sola entidad mapear a múltiples 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.

0

Si esto no es un fuera y ves que necesitas para contar un número de diferentes entidades asociadas, una vista de base de datos podría ser un simple (y potencialmente más apropiado) elección:

  1. Crear su vista de base de datos.

    Suponiendo que se desea que todas las propiedades de la entidad original más el número de mensajes asociada: (. Si está utilizando Entity Framework Código primeras migraciones, véase this SO answer sobre cómo crear una vista)

    CREATE VIEW DiscussionCategoryWithStats AS 
    SELECT dc.*, 
         (SELECT count(1) FROM Messages m WHERE m.DiscussionCategoryId = dc.Id) 
          AS MessageCount 
    FROM DiscussionCategory dc 
    

  2. En EF, basta con utilizar la vista en lugar de la entidad original:

    // You'll need to implement this! 
    DiscussionCategoryWithStats dcs = _repository.GetDiscussionCategoryWithStats(id); 
    
    int i = dcs.MessageCount; 
    ... 
    
5

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.

Estoy asumiendo modelos POCO 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 padre 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.

  1. añadir un alojamiento marcador de posición para el modelo de clase mediante la anotación de datos [NotMapped]System.ComponentModel.DataAnnotations.Schema del espacio de nombres. 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; } 
        } 
    } 
    
  2. En su controlador, obtenga la lista de objetos principales.

    var discussions = db.Discussions.ToList(); 
    
  3. Capture the counts en un diccionario. Esto genera una sola consulta SQL GROUP BY con todas las ID principales y recuentos de objetos secundarios. (Presumiendo DiscussionID es el FK en Messages.)

    var _counts = db.Messages.GroupBy(m => m.DiscussionID).ToDictionary(d => d.Key, d => d.Count()); 
    
  4. bucle a través de los objetos primarios, buscar el recuento del diccionario, y almacenar en la propiedad del marcador de posición.

    foreach (var d in discussions) 
        { 
         d.MessageCount = (_counts.ContainsKey(d.ID)) ? _counts[d.ID] : 0; 
        } 
    
  5. Regrese a la lista de discusión.

    return View(discussions); 
    
  6. referencia a la propiedad MessageCount en la vista.

    @foreach (var item in Model) { 
        ... 
        @item.MessageCount 
        ... 
    } 
    

Sí, se podría sólo cosas que diccionario en el ViewBag y hacer la búsqueda directamente en la vista, pero que enturbia la vista con código que no necesita estar allí.

Al final, desearía que EF tuviera una manera de hacer un "conteo perezoso". El problema con la carga lenta y explícita es que está 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 al @item.Messages.Count desde la Vista sin tener que preocuparse de cargar potencialmente toneladas de datos de objetos no deseados.

Espero que esto ayude.

Cuestiones relacionadas