asp.net - patrón - ¿Ventajas de crear un repositorio genérico frente a un repositorio específico para cada objeto?
patrón repositorio entity framework (6)
De hecho estoy en desacuerdo con la publicación de Bryan. Creo que tiene razón, que al final todo es muy único, y así sucesivamente. Pero, al mismo tiempo, la mayor parte de eso aparece cuando diseñas, y creo que al obtener un repositorio genérico y usarlo mientras desarrollé mi modelo, puedo obtener una aplicación rápidamente, luego refactorizar a una mayor especificidad cuando encuentro el necesito hacerlo
Entonces, en casos como ese, a menudo he creado un IRepository genérico que tiene la pila CRUD completa, y eso me permite jugar rápidamente con la API y dejar que la gente juegue con la interfaz de usuario y realizar pruebas de integración y aceptación del usuario en paralelo. Luego, cuando encuentro que necesito consultas específicas en el repositorio, etc., comienzo a reemplazar esa dependencia con la específica, si es necesario, y desde allí. Una impl subyacente. es fácil de crear y utilizar (y posiblemente enganchar a un archivo en memoria db u objetos estáticos o objetos burlados o lo que sea).
Dicho eso, lo que comencé últimamente es romper el comportamiento. Entonces, si hace interfaces para IDataFetcher, IDataUpdater, IDataInserter e IDataDeleter (por ejemplo) puede mezclar y combinar para definir sus requisitos a través de la interfaz y luego tener implementaciones que se encargan de algunos o todos ellos, y yo puedo Todavía inyecte la implementación de "hágalo todo" para usar mientras estoy desarrollando la aplicación.
Pablo
Estamos desarrollando una aplicación ASP.NET MVC, y ahora estamos creando las clases de repositorio / servicio. Me pregunto si hay ventajas importantes para crear una interfaz genérica de IRepository que implementen todos los repositorios, frente a cada repositorio que tiene su propia interfaz y conjunto de métodos únicos.
Por ejemplo: una interfaz genérica de IRepository podría parecerse (tomada de esta respuesta ):
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Cada repositorio implementaría esta interfaz, por ejemplo:
- CustomerRepository: IRepository
- ProductRepository: IRepository
- etc.
La alternativa que hemos seguido en proyectos anteriores sería:
public interface IInvoiceRepository : IDisposable
{
EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
InvoiceEntity CreateInvoice();
InvoiceLineEntity CreateInvoiceLine();
void SaveChanges(InvoiceEntity); //handles inserts or updates
void DeleteInvoice(InvoiceEntity);
void DeleteInvoiceLine(InvoiceLineEntity);
}
En el segundo caso, las expresiones (LINQ u otras) estarían contenidas por completo en la implementación del Repositorio, quienquiera que esté implementando el servicio solo necesita saber a qué función del repositorio llamar.
Supongo que no veo la ventaja de escribir toda la sintaxis de expresión en la clase de servicio y pasar al repositorio. ¿No significaría esto que el código LINQ fácil de destruir se está duplicando en muchos casos?
Por ejemplo, en nuestro antiguo sistema de facturación, llamamos
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
de algunos servicios diferentes (Cliente, Factura, Cuenta, etc.). Eso parece mucho más limpio que escribir lo siguiente en varios lugares:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
La única desventaja que veo al usar el enfoque específico es que podríamos terminar con muchas permutaciones de funciones Get *, pero esto todavía parece preferible a empujar la lógica de expresión hacia las clases de Servicio.
¿Qué me estoy perdiendo?
Este es un problema tan antiguo como el propio Repositorio. La reciente introducción de IQueryable de LINQ, una representación uniforme de una consulta, ha provocado mucha discusión sobre este tema.
Prefiero repositorios específicos, después de haber trabajado muy duro para construir un marco de repositorio genérico. No importa qué mecanismo inteligente probé, siempre terminé en el mismo problema: un repositorio es una parte del dominio que se está modelando, y ese dominio no es genérico. No se pueden eliminar todas las entidades, no se pueden agregar todas las entidades, no todas las entidades tienen un repositorio. Las consultas varían ampliamente; la API del repositorio se vuelve tan única como la propia entidad.
Un patrón que uso a menudo es tener interfaces de repositorio específicas, pero una clase base para las implementaciones. Por ejemplo, usando LINQ to SQL, podrías hacer:
public abstract class Repository<TEntity>
{
private DataContext _dataContext;
protected Repository(DataContext dataContext)
{
_dataContext = dataContext;
}
protected IQueryable<TEntity> Query
{
get { return _dataContext.GetTable<TEntity>(); }
}
protected void InsertOnCommit(TEntity entity)
{
_dataContext.GetTable<TEntity>().InsertOnCommit(entity);
}
protected void DeleteOnCommit(TEntity entity)
{
_dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
}
}
Reemplace DataContext
con su unidad de trabajo de su elección. Un ejemplo de implementación podría ser:
public interface IUserRepository
{
User GetById(int id);
IQueryable<User> GetLockedOutUsers();
void Insert(User user);
}
public class UserRepository : Repository<User>, IUserRepository
{
public UserRepository(DataContext dataContext) : base(dataContext)
{}
public User GetById(int id)
{
return Query.Where(user => user.Id == id).SingleOrDefault();
}
public IQueryable<User> GetLockedOutUsers()
{
return Query.Where(user => user.IsLockedOut);
}
public void Insert(User user)
{
InsertOnCommit(user);
}
}
Tenga en cuenta que la API pública del repositorio no permite que los usuarios sean eliminados. Además, exponer IQueryable
es una lata de gusanos: hay tantas opiniones como botones de ombligo sobre ese tema.
Greg Young tuvo una excelente publicación sobre este tema. http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/
Prefiero los repositorios específicos que se derivan del repositorio genérico (o la lista de repositorios genéricos para especificar el comportamiento exacto) con firmas de métodos invalidables.
Tener un repositorio genérico que esté envuelto por un repositorio específico. De esta forma puede controlar la interfaz pública pero aún así tener la ventaja de la reutilización de código que proviene de tener un repositorio genérico.
clase pública UserRepository: Repository, IUserRepository
¿No debería inyectar IUserRepository para evitar exponer la interfaz? Como han dicho las personas, es posible que no necesites la pila CRUD completa, etc.