He utilizado una mezcla de # 2 y # 3, pero prefiero un repositorio genérico estricto si es posible (más estricto que incluso sugerido en el enlace para # 3). # 1 no es bueno porque funciona mal con pruebas unitarias.
Si tiene un dominio más pequeño o necesita restringir qué entidades le permiten consultar su dominio, supongo que el # 2- o # 3 que define las interfaces de repositorio específicas de cada entidad que implementan un repositorio genérico tiene sentido. Sin embargo, considero que es agotador e innecesario escribir una interfaz y una implementación concreta para cada entidad que deseo consultar. ¿De qué sirve el public interface IFooRepository : IRepository<Foo>
(nuevamente, a menos que deba restringir a los desarrolladores a un conjunto de raíces agregadas permitidas)?
acabo de definir la interfaz de mi repositorio genérico, con Add
, Remove
, Get
, GetDeferred
, Count
y Find
métodos (Encontrar devuelve una interfaz que permite IQueryable
LINQ), cree una aplicación genérica concreta, y lo llaman un día. Confío mucho en Find
y, por lo tanto, LINQ. Si necesito usar una consulta específica más de una vez, utilizo métodos de extensión y escribo la consulta usando LINQ.
Esto cubre el 95% de mis necesidades de persistencia. Si necesito realizar algún tipo de acción de persistencia que no se puede hacer de forma genérica, uso una API de ICommand
de fabricación propia. Por ejemplo, supongamos que estoy trabajando con NHibernate y necesito realizar una consulta compleja como parte de mi dominio, o tal vez necesite hacer un comando masivo. La API se ve más o menos así:
// marker interface, mainly used as a generic constraint
public interface ICommand
{
}
// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
void Execute();
}
// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
TResult Execute();
}
// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
int Count();
}
// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
TCommand Create<TCommand>() where TCommand : ICommand;
}
Ahora puedo crear una interfaz para representar un comando específico.
public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
Decimal MinimumBalance { get; set; }
}
puedo crear una aplicación concreta y utilizar SQL crudo, NHibernate HQL, lo que sea, y registrarlo con mi localizador de servicios.
Ahora en mi lógica de negocio que puede hacer algo como esto:
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
También puede utilizar un patrón Especificación con IQuery
para construir, fácil de entrada impulsada consultas significativas, en lugar de tener una interfaz con millones propiedades confusas, pero eso supone que no encuentra el patrón de especificación confuso en sí mismo;).
Una última parte del rompecabezas es cuando su repositorio necesita hacer operaciones específicas de depósito previo y posterior. Ahora, puede crear fácilmente una implementación de su repositorio genérico para una entidad específica, luego anular los métodos relevantes y hacer lo que necesita hacer, y actualizar su IoC o registro de localizador de servicios y finalizarlo.
Sin embargo, a veces esta lógica es transversal e incómoda de implementar anulando un método de repositorio. Así que creé IRepositoryBehavior
, que básicamente es un receptor de eventos. (Continuación es solo una definición aproximada de la parte superior de mi cabeza)
public interface IRepositoryBehavior
{
void OnAdding(CancellableBehaviorContext context);
void OnAdd(BehaviorContext context);
void OnGetting(CancellableBehaviorContext context);
void OnGet(BehaviorContext context);
void OnRemoving(CancellableBehaviorContext context);
void OnRemove(BehaviorContext context);
void OnFinding(CancellableBehaviorContext context);
void OnFind(BehaviorContext context);
bool AppliesToEntityType(Type entityType);
}
Ahora, estos comportamientos pueden ser cualquier cosa. Auditoría, comprobación de seguridad, borrado suave, imposición de restricciones de dominio, validación, etc. Creo un comportamiento, lo registro con el IoC o localizador de servicios, y modifico mi repositorio genérico para tomar una colección de IRepositoryBehavior
s registrados, y verifico cada comportamiento contra el tipo de repositorio actual y ajuste la operación en los controladores pre/post para cada comportamiento aplicable.
Aquí hay un ejemplo de comportamiento de borrado suave (borrado suave significa que cuando alguien solicita eliminar una entidad, simplemente la marcamos como eliminada para que no se pueda devolver nuevamente, pero en realidad nunca se elimina físicamente).
public SoftDeleteBehavior : IRepositoryBehavior
{
// omitted
public bool AppliesToEntityType(Type entityType)
{
// check to see if type supports soft deleting
return true;
}
public void OnRemoving(CancellableBehaviorContext context)
{
var entity = context.Entity as ISoftDeletable;
entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated
context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
}
}
Sí, esto es básicamente una aplicación simplificada y abstracta de los detectores de eventos de NHibernate, pero por eso me gusta.A) Puedo probar un comportamiento sin poner NHibernate en la imagen B) Puedo usar estos comportamientos fuera de NHibernate (digamos que el repositorio es la implementación del cliente que envuelve las llamadas al servicio REST) C) Los oyentes del evento de NH pueden ser un verdadero dolor en el culo ;)
Yo diría que el enlace en el número 2 no es un patrón de repositorio típico. Normalmente, tiene un repositorio para cada Raíz agregada en [DDD] (http://en.wikipedia.org/wiki/Domain-driven_design) speak. Este es un buen [SO thread] (http://stackoverflow.com/questions/1958621/whats-an-aggregate-root) sobre este tema. El ejemplo en el número 2, como usted menciona, parece simplemente envolver una tabla. Parece que están implementando el patrón solo para implementar el patrón sin ningún beneficio real. Entonces estaría de acuerdo contigo. – RobertMS
Puede que tengas razón. Sin embargo, al buscar en la web, la mayoría de los ejemplos que encontré crearon envoltorios separados para cada entidad, incluidos los de algunos libros que tengo. En ese sentido, el código en el enlace que publiqué parecía típico. Gracias por los enlaces. Los veré. –
@JonathanWood Aquí está la [solución que más me gusta] (http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/) (maldición, yo uso este enlace mucho). A saber, la interfaz de repositorio no genérica con métodos genéricos. Todavía es una envoltura relativamente delgada alrededor del 'DbContext', pero permite una prueba más sencilla. –