2012-03-22 13 views
6

En escenarios de aplicaciones web, comúnmente hay dos requisitos con respecto a la recuperación de datos: 1. Todas las consultas deben ser buscadas opcionalmente 2. Hay muchas consultas de "filtrado" necesarias para el usuario (ej. Persona de búsqueda por nombre y correo electrónico y edad) 3. Clasificación de consultas implícitas en algunos rejilla clientsideEstrategias de consulta de NHibernate en aplicaciones web

Y, por supuesto, el caso combinado: filtrada, paginado consultas que se clasifican :)

Estos requisitos pueden conducir a una gran cantidad de métodos de repositorio en la capa de datos, cada uno con una gran cantidad de parámetros. ¿Existen patrones comunes que den a este proceso un comportamiento más dinámico (por ejemplo, consultas autogeneradas según las propiedades de la clase de dominio)?

Soy consciente del hecho de que los métodos de repositorio deben estar limpios y bien definidos. Pero en este momento, tengo ganas de escribir una gran cantidad de código repetitivo cada vez que agrego algún formulario de filtrado a una vista MVC o alguna tabla paginada ordenable.

¿Cómo manejan otros desarrolladores tales requisitos?

gracias de antemano

Respuesta

7

Repositorios, generic repositories y repositorios que exponen IQueryable es un debate muy acalorado.

La línea de fondo es un repositorio que es genérico o expone IQueryable no es un repositorio real en absoluto, es simplemente una abstracción de su capa de datos.

Ahora bien, esto no es malo, pero no lo llames un repositorio, llámalo como es. Una abstracción de capa de datos le permite insertar rápidamente algo en su UI donde lee y escribe entidades sin filtrar el marco de capa de datos en la interfaz de usuario. Por supuesto, puede simplemente inyectar un ISession y terminarlo.

public interface IRepository<T> {} 

public class NHibernateRepository<T> : IRepository<T> 
{ 
    private ISession session; 

    public NHibernateRepository(ISession session) 
    { 
     this.session = session; 
    } 

    T Get(object id) { return session.GetById<T>(id)); } 

    IQueryable<T> Query { get { return session.Query<T>(); } 
} 

new NHibernateRepository<Customer>(session).Query().Where(customer => customer.Name == Fred); 

Sin embargo, si se desea capturar algo de lógica reutilizable y proporcionar una clara contrato entre sus servicios o la interfaz de usuario y su capa de datos a continuación, un Repository hace precisamente eso. Usted define métodos claros que dicen lo que está recuperando y cómo.Además, con un repositorio que solo desea exponer su aggregate roots, estas son las entidades raíz a las que cuelgan todos sus otros datos, estos pueden ser cosas como Customer y Supplier. No trataría de llegar a una dirección directamente, cargaría un cliente y luego consultaría sus direcciones. Cargará una lista de proveedores en función de las cosas que suministren, pero no accederá a través de un 'ItemsRepository'. Mis ejemplos pueden no ser los mejores, pero te dan una idea.

public class CustomerRepository 
{ 
    public Customer GetCustomerWithName(string name); 
} 

public class SupplierRepository 
{ 
    public IEnumerable<Supplier> GetSuppliersWhoStockItem(string itemName) 
} 

Por último, si su aventura es posible que desee mirar a CQRS, que es grande de un sujeto a esbozar aquí, pero hay un montón de ejemplos.

El primero es más rápido de implementar, el segundo le da un código reutilizable más claro, el tercero le da una separación entre su capa UI pero requiere más trabajo de tierra. Depende de lo que necesita y desea, y probablemente deba abordarse en ese orden.

+0

Gracias Bronumski , Creo que tomaré un enfoque combinado: métodos bien definidos llamados por los servicios web y una interfaz IQueryable adicional para manejar consultas genéricas desde la interfaz web. – mbue

1

Se podría hacer su repositorio proporcionar IQueryable y dejar que el ActionMethod averiguar lo que se vea. Por ejemplo:

public System.Linq.IQueryable<Models.MyModel> Query() 
    { 
     return mSession.Query<Models.MyModel>(); 
    } 
3

En nuestro proyecto también hemos utilizado repositorios, repositorio por entidad, pero no me gusta. Puede causar muchos problemas cuando escribe consultas complejas con muchas entidades interactivas. Creo que va a ser un repositorio de uno genérico para operaciones de base, y todas las consultas debería estar presente con el modelo de objetos de consulta, algo así como clase separada por consulta, aunque vistazo a:

http://richarddingwall.name/2010/06/15/brownfield-cqrs-part-1-commands/

http://codebetter.com/gregyoung/2009/01/20/ddd-specification-or-query-object/

http://devlicio.us/blogs/casey/archive/2009/02/13/ddd-command-query-separation-as-an-architectural-concept.aspx

http://blog.jonathanoliver.com/2009/10/dddd-why-i-love-cqrs/

http://www.udidahan.com/2007/03/28/query-objects-vs-methods-on-a-repository/

3

Utiliza un repositorio para enumerar una raíz agregada. A menudo, sus controladores trabajan con una raíz agregada, que puede filtrar, ordenar, etc., según lo requiera el usuario.

Por lo tanto, utilizo un repositorio en la línea de lo que ya se ha mencionado.

Sin embargo, a veces tengo que trabajar dentro de especificaciones más complejas, donde usar una raíz agregada y/o un montón de repositorios es doloroso, ineficiente o simplemente imposible. Por ejemplo, es posible que deba ejecutar un informe comercial grande, o tal vez ejecutar un comando por lotes.

En este tipo de casos, también he definido ICommand/IQuery, con una aplicación de base de NH cuidar el material de fontanería (como genérico Repository hace).

Lo que hago es hacer una interfaz que represente el contrato de la especificación, exponiendo a los miembros que pueda necesitar para ayudarme a construir los parámetros necesarios. Luego realizo una implementación de esa especificación, usando NH como columna vertebral, cumpliendo con la especificación usando la técnica más adecuada (una declaración HQL, SQL sin procesar, Criteria, QueryOver ... lo que sea).

Aquí hay una tosca ilustración de lo que quiero decir. Tenga en cuenta que utilizo un ICommandProvider arbitrario, que es un objeto que crea nuevas instancias de un comando según sea necesario (en caso de que necesite emitir varios comandos en una operación). Registraría mis comandos con un IoC y haré que el proveedor trabaje con él para crear instancias de comando.

public interface ICommand 
{ 

} 

public interface ICommandProvider 
{ 
    TCommand Create<TCommand>() 
     where TCommand : ICommand; 

} 

public interface IQuery<TResult> : ICommand 
{ 
    TResult Execute(); 
} 

public class NhCommand : ICommand 
{ 
    // plumbing stuff here, like finding the current session 
} 

public class DelinquentAccountViewModel 
{ 
    public string AccountName { get; set; } 
    public decimal Amount { get; set; } 
} 

public interface IDelinquentAccountsQuery : IQuery<IEnumerable<DelinquentAccountViewModel>> 
{ 
    void AmountGreaterThan(decimal amount); 
    // you could define members for specifying sorting, etc. here 
} 

public class DelinquentAccountsQuery : NhCommand 
{ 
    public IEnumerable<DelinquentAccountViewModel> Execute() 
    { 
     // build HQL and execute results, resulting in a list of DelinquentAccountViewModels 
     // using _amountGreaterThan as a parameter 
     return null; 
    } 

    private Decimal _amountGreaterThan; 

    public void AmountGreaterThan(Decimal amount) 
    { 
     _amountGreaterThan = amount; 
    } 
} 

Uso de un controlador podría ser algo como esto:

public class DelinquentAccountsController : Controller 
{ 
    protected ICommandProvider CommandProvider { get; private set; } 

    public DelinquentAccountsController(ICommandProvider commandProvider) 
    { 
     CommandProvider = commandProvider; 
    } 

    public ActionResult Index(decimal amount) 
    { 
     var query = CommandProvider.Create<IDelinquentAccountsQuery>(); 
     query.AmountGreaterThan(amount); 
     return View(query.Execute()); 

    } 
} 

Nada dice que no puede hacer todo de su acceso a los datos mediante un comando/consulta, pero es más trabajo de lo que necesito. Encuentro que el enfoque de repositorio estándar (usando LINQ contra NHibernate) maneja aproximadamente el 95% del acceso a datos que requieren mis aplicaciones.

1

Imho orm ya es suficiente abstracción. No necesitas repositorios además de eso. Solo necesitas abstracción si vas a cambiar orm sobre la marcha con algún ajuste. Donde, Skip, Take, OrderBy, etc. son independientes de los términos y pueden ser utilizados al exponer IQueryable. Pero algunas características son orm específicas (como buscar vs.incluyen) y los que hacen repositorios muy feos (millones métodos o método con el millón de parámetros)

por lo general sólo hacen los métodos de extensión

public static class QueryExtensions 
{ 
    public static IQueryable<T> Published(this IQueryable<T> pages) where T : IPage 
    { 
     return pages.Where(p => p.State == PageState.Public && p.Published <= DateTime.UtcNow); 
    } 
    public static IQueryable<T> By(this IQueryable<T> pages, User author) where T : IPage 
    { 
     return pages.Where(p => p.Author == author); 
    } 

    public static IEnumerable<Foo> AdvancedThing(this ISession session, string text) 
    { 
     // I get all the power of NHibernate :) 
     return session.CreateQuery("...").SetString("text", text).List<Foo>(); 
    } 
} 

y uso ISession directamente en los métodos de acción

var posts = session.Query<Post>().By(user).Published().FetchMany(p => p.Tags).ToList(); 
Cuestiones relacionadas