2009-03-07 20 views
5

¿Cuál es la forma recomendada de devolver datos ad hoc (personalizado caso por caso) del repositorio que no se ajustan a ninguna entidad modelo o que se extienden un poco?Datos ad hoc y patrón de repositorio

El ejemplo 101 sería la aplicación de palabra omnipresente hello: un sistema de blog. Supongamos que desea cargar una lista de publicaciones donde la entrada de entrada contiene información adicional que no existe en la entidad de publicación. Digamos que es la cantidad de comentarios y la fecha y hora del último comentario. Esto sería muy trivial si uno estuviera usando el viejo SQL simple y leyendo datos directamente de la base de datos. ¿Cómo se supone que debo hacerlo de manera óptima utilizando el patrón de repositorio si no puedo pagar la colección completa de Comentarios para cada publicación, y quiero hacerlo en un solo hit de la base de datos? ¿Hay algún patrón comúnmente utilizado para esta situación? Ahora imagine que tiene una aplicación web moderadamente compleja donde cada página necesita datos personalizados ligeramente diferentes, y no es posible cargar jerarquías completas (rendimiento, requisitos de memoria, etc.).

Algunas ideas al azar:

  1. añadir una lista de propiedades para cada modelo que podría ser poblada por los datos personalizados.

  2. La subclase modela las entidades caso por caso y crea lectores personalizados para cada subclase.

  3. Usa LINQ, redacta consultas ad hoc y lee clases anónimas.

Nota: He hecho una similar question recently, pero parecía ser demasiado general y no atrajo mucha atención.

Ejemplo:

Sobre la base de las sugerencias de respuestas a continuación, añado un ejemplo más concreto. Aquí está la situación que estaba tratando de describir:

IEnumarable<Post> posts = repository.GetPostsByPage(1); 
foreach (Post post in posts) 
{ 

    // snip: push post title, content, etc. to view 

    // determine the post count and latest comment date 
    int commentCount = post.Comments.Count(); 
    DateTime newestCommentDate = post.Comments.Max(c => c.Date); 

    // snip: push the count and date to view 

} 

Si no hago nada extra y utilizar un ORM de la plataforma, esto dará lugar a n + 1 consultas o, posiblemente, una consulta de carga todos los mensajes y comentarios . Pero de manera óptima, me gustaría poder ejecutar solo un SQL que devolvería una fila para cada publicación, incluido el título de la publicación, el cuerpo, etc. y el recuento de comentarios y la fecha de comentario más reciente en la misma. Esto es trivial en SQL. El problema es que mi repositorio no podrá leer y ajustar este tipo de datos en el modelo. ¿A dónde van las fechas máximas y los recuentos?

No estoy preguntando cómo hacer eso. Siempre puede hacerlo de alguna manera: agregue métodos extra al repositorio, agregue nuevas clases, entidades especiales, use LINQ etc., pero supongo que mi pregunta es la siguiente. ¿Cómo es posible que el patrón de repositorio y el desarrollo impulsado por el modelo apropiado sean tan ampliamente aceptados, pero aún así no parecen abordar este caso aparentemente tan común y básico?

Respuesta

0

No se puede decir que realmente veo cuál es el problema, simplemente disparando al aire aquí:

  • Añadir una entidad específica para encapsular la información yo quiero
  • añadir un alojamiento Comentarios al Post.(No veo por qué esto requeriría que busque todos los comentarios; puede buscar los comentarios para la publicación en particular que está cargando)
  • Utilice la carga diferida para obtener solo los comentarios cuando acceda a la propiedad

Creo que tendrías una mayor probabilidad de ver tu pregunta respondida si hicieras específica la plataforma, el lenguaje y el mapeador O/R (parece ser .NET C# o VB, ya que mencionaste LINQ. LINQ 2 SQL? Entity framework ? Algo más?)

+0

Gracias por indicarme eso. He agregado un ejemplo concreto simple y más explicaciones. –

1

Hay mucho en esta pregunta. ¿Necesita esta información específica para un procedimiento de informe? Si es así, entonces la solución adecuada es tener acceso a datos por separado para los informes. Bases de datos, vistas, ect.

¿O es una consulta ad-hoc? Si es así, Ayende tiene una publicación sobre este mismo problema. http://ayende.com/Blog/archive/2006/12/07/ComplexSearchingQueryingWithNHibernate.aspx

Utiliza un objeto "Finder". Está usando NHibernate, así que, esencialmente, lo que hace es crear una consulta separada.

He hecho algo similar en el pasado al crear un objeto Query que puedo llenar antes de entregarlo a un repositorio (algunos puristas DDD argumentarán en contra, pero me parece elegante y fácil de usar).

el objeto de consulta implementa una interfaz fluida, así que puede escribir esto y obtener los resultados:

IQuery query = new PostQuery() 
    .WithPostId(postId) 
    .And() 
    .WithCommentCount() 
    .And() 
    .WithCommentsHavingDateLessThan(selectedDate); 


Post post = _repository.Find(query); 

Sin embargo, en su caso específico me pregunto en su diseño. Estás diciendo que no puedes cargar los comentarios con la publicación. ¿Por qué? ¿Estás siendo demasiado preocupante sobre el rendimiento? ¿Es este un caso de optimización prematura? (me parece)

Si tuviera un objeto Post sería mi raíz de agregado y vendría con los Comentarios adjuntos. Y luego, todo lo que quieras hacer funcionará en todos los escenarios.

+0

Gracias. Tus sugerencias parecen un buen comienzo. Me pregunto dónde guardas el comentario realmente. Seguramente, no hay un miembro de datos por separado en la entidad Post. –

+0

En cuanto al rendimiento, el ejemplo con las publicaciones del blog fue solo un ejemplo. La aplicación real que tengo en mente ya se está ejecutando, y no podemos permitirnos cargar todas las colecciones. –

+0

El dominio del problema real hace la diferencia. Es imposible que alguien sepa si tiene un defecto de diseño en su dominio, lo que en realidad podría conducir a una mejor solución. Publicaciones y comentarios es un problema de resolución, y sus preguntas no tienen sentido contextual. –

1

Como necesitábamos solucionar urgentemente el problema que describí en mi pregunta original, recurrimos a la siguiente solución. Agregamos una colección de propiedades (un diccionario) a cada entidad modelo, y si el DAL lo necesita, almacena datos personalizados en. Para establecer algún tipo de control, la colección de propiedades está marcada por instancias de una clase designada y solo admite tipos de datos simples (enteros, fechas, ...) que es todo lo que necesitamos para el movimiento, y lo más probable es que alguna vez necesitemos . Un caso típico que esto resuelve es cargar una entidad con recuentos para sus subcolecciones en lugar de colecciones completas llenas. Sospecho que esto probablemente no recibe ningún premio por un diseño de software, pero fue la solución más simple y práctica para nuestro caso.

+0

Creo que la otra opción habría sido adjuntar algunas consultas con nombre a las entidades y usarlas en los repositorios. Sin embargo, una pregunta interesante, lástima que pocas personas parecieran asimilarla. – wds

0

Si no está encerrado en un RDBM, entonces una base de datos como CouchDB o Amazons SimpleDB puede ser algo a tener en cuenta. Lo que estás describiendo es trivial en una Vista de CouchDB. Probablemente, esto no responda tu pregunta específica, pero a veces es bueno mirar opciones radicalmente diferentes.

0

Para esto generalmente tengo un RepositoryStatus y una clase de estado que actúa como mi Data Transfer Object (DTO). La clase de estado se usa en la capa de servicio de mi aplicación (por la misma razón) de la cual hereda el RepositoryStatus. Luego, con esta clase, puedo devolver mensajes de error, objetos de respuesta, etc. de la capa Repositorio. Esta clase es genérica, ya que aceptará cualquier objeto y lo expulsará para el receptor.

Aquí es la clase de estado:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using RanchBuddy.Core.Domain; 
using StructureMap; 

namespace RanchBuddy.Core.Services.Impl 
{ 
    [Pluggable("Default")] 
    public class Status : IStatus 
    { 
     public Status() 
     { 
      _messages = new List<string>(); 
      _violations = new List<RuleViolation>(); 
     } 

     public enum StatusTypes 
     { 
      Success, 
      Failure 
     } 

     private object _object; 
     public T GetObject<T>() 
     { 
      return (T)_object; 
     } 
     public void SetObject<T>(T Object) 
     { 
      _object = Object; 
     } 

     private List<string> _messages; 
     public void AddMessage(string Message) 
     { 
      _messages.Add(Message); 
     } 
     public List<string> GetMessages() 
     { 
      return _messages; 
     } 
     public void AddMessages(List<string> Messages) 
     { 
      _messages.AddRange(Messages); 
     } 

     private List<RuleViolation> _violations; 
     public void AddRuleViolation(RuleViolation violation) 
     { 
      _violations.Add(violation); 
     } 
     public void AddRuleViolations(List<RuleViolation> violations) 
     { 
      _violations.AddRange(violations); 
     } 
     public List<RuleViolation> GetRuleViolations() 
     { 
      return _violations; 
     } 
     public StatusTypes StatusType { get; set; } 
    } 
} 

Y aquí es el RepositoryStatus:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using RanchBuddy.Core.Services.Impl; 
using StructureMap; 

namespace RanchBuddy.Core.DataAccess.Impl 
{ 
    [Pluggable("DefaultRepositoryStatus")] 
    public class RepositoryStatus : Status, IRepositoryStatus 
    { 

    } 
} 

Como se puede ver la RepositoryStatus aún no hace nada especial y simplemente se basa en los objetos de estado utilidades ¡Pero quería reservar el derecho de extenderme en una fecha posterior!

Estoy seguro de que algunos de los recalcitrantes afirmarán que esto no debería usarse si vas a ser un acusador ... sin embargo, sé tu dolor porque a veces tienes que desmayar más que solo un objeto devuelto!

Cuestiones relacionadas