tipos que patron para inyección inyeccion injection dummies dependency dependencias dependencia control design entity-framework dependency-injection structuremap dispose

design - que - Cómo disponer recursos con inyección de dependencia



que es la inyección de dependencia (3)

En lugar de agregar Dispose a IMyRepository, podría declarar IMyRepository así:

public interface IMyRepository: IDisposable { SomeClass GetById(int id); }

De esta forma, se asegura de que todos los repositorios llamen a Dispose algunas veces, y puede usar el patrón C # "using" en un objeto Repository:

using (IMyRepository rep = GetMyRepository(...)) { ... do some work with rep }

Estoy usando StructureMap para resolver las referencias a mi clase de repositorio. Mi interfaz de repositorio implementa IDisposable, por ejemplo

public interface IMyRepository : IDisposable { SomeClass GetById(int id); }

Una implementación de la interfaz utilizando Entity Framework:

public MyRepository : IMyRepository { private MyDbContext _dbContext; public MyDbContext() { _dbContext = new MyDbContext(); } public SomeClass GetById(int id) { var query = from x in _dbContext where x.Id = id select x; return x.FirstOrDefault(); } public void Dispose() { _dbContext.Dispose(); } }

De todos modos, como he mencionado, estoy usando StructureMap para resolver IMyRepository. Entonces , ¿ cuándo, dónde y cómo debo llamar a mi método de eliminación?


Si quieres hacerlo bien, te aconsejaría sobre un par de cambios:

1 - No tiene instancias privadas del contexto de datos en el repositorio. Si trabajas con varios repositorios, terminarás teniendo múltiples contextos.

2 - Para resolver lo anterior, ajuste el contexto en una Unidad de trabajo . Pase la unidad de trabajo a los repositorios a través del ctor: public MyRepository(IUnitOfWork uow)

3 - Hacer que la unidad de trabajo implemente IDisposable. La Unidad de trabajo debe "actualizarse" cuando comienza una solicitud y, por lo tanto, debe eliminarse cuando finaliza la solicitud. El Repositorio no debe implementar IDisposable, ya que no está trabajando directamente con los recursos, simplemente los está mitigando. El DataContext / Unit of Work debería implementar IDispoable.

4 - Suponiendo que está utilizando una aplicación web, no necesita hacer una llamada explícita para deshacerse de usted - repito, no necesita llamar explícitamente a su método de eliminación . StructureMap tiene un método llamado HttpContextBuildPolicy.DisposeAndClearAll(); . Lo que hace esto es invocar el método "Dispose" en cualquier objeto de ámbito HTTP que implemente IDisposable. Ponga esta llamada en Application_EndRequest (Global.asax). Además, creo que hay un método actualizado, llamado ReleaseAllHttpScopedObjects o algo así, no recuerdo el nombre.


Advertencia : tenga en cuenta que mis puntos de vista han cambiado, y debe considerar los siguientes consejos desactualizados. Por favor, lea la actualización al final.

Mientras que los marcos DI pueden administrar la vida útil de los objetos para usted y algunos incluso podrían disponer de los objetos para usted una vez que haya terminado de usarlos, esto hace que la eliminación de los objetos sea demasiado implícita. La interfaz IDisposable se crea porque existía la necesidad de una limpieza determinista de los recursos. Por lo tanto, en el contexto de DI, personalmente me gusta que esta limpieza sea muy explícita. Cuando lo haces explícito, básicamente tienes dos opciones: 1. Configura la DI para devolver objetos transitorios y deshazte de estos objetos tú mismo. 2. Configure una fábrica e indique a la fábrica que cree nuevas instancias.

Yo prefiero el segundo acercamiento al primero, porque especialmente cuando se usa Dependency Injection, su código no es tan limpio como podría serlo. Busque por ejemplo en este código:

public sealed class Client : IDisposable { private readonly IDependency dependency; public Client(IDependency dependency) { this. dependency = dependency; } public void Do() { this.dependency.DoSomething(); } public Dispose() { this.dependency.Dispose(); } }

Si bien este código explícitamente dispone de la dependencia, podría levantar algunas cejas a los lectores, ya que los recursos normalmente solo deberían ser eliminados por el propietario del recurso. Aparentemente, el Client convirtió en el propietario del recurso cuando se lo inyectó.

Debido a esto, estoy a favor del uso de una fábrica. Busque por ejemplo en este ejemplo:

public sealed class Client { private readonly IDependencyFactory factory; public Client(IDependencyFactory factory) { this.factory = factory; } public void Do() { using (var dependency = this.factory.CreateNew()) { dependency.DoSomething(); } } }

Este ejemplo tiene exactamente el mismo comportamiento que el ejemplo anterior, pero vea cómo la clase Client ya no tiene que implementar IDisposable , ya que crea y dispone el recurso dentro del método Do

Inyectar una fábrica es la forma más explícita (la ruta de menor sorpresa) para hacer esto. Es por eso que prefiero este estilo. Lo malo de esto es que a menudo necesitas definir más clases (para tus fábricas), pero personalmente no me importa.

RPM1984 pidió un ejemplo más concreto.

No quisiera que el repositorio implemente IDisposable , pero tengo una Unidad de trabajo que implementa IDisposable , controla / contiene repositorios y tiene una fábrica que sabe cómo crear una nueva unidad de obras. Con eso en mente, el código anterior se vería así:

public sealed class Client { private readonly INorthwindUnitOfWorkFactory factory; public Client(INorthwindUnitOfWorkFactory factory) { this.factory = factory; } public void Do() { using (NorthwindUnitOfWork db = this.factory.CreateNew()) { // ''Customers'' is a repository. var customer = db.Customers.GetById(1); customer.Name = ".NET Junkie"; db.SubmitChanges(); } } }

En el diseño que uso, y he descrito here , utilizo una clase concreta NorthwindUnitOfWork que envuelve un IDataMapper que es la puerta de entrada al proveedor LINQ subyacente (como LINQ to SQL o Entity Framework). En resumen, el diseño es el siguiente:

  1. Un INorthwindUnitOfWorkFactory se inyecta en un cliente.
  2. La implementación particular de esa fábrica crea una clase concreta NorthwindUnitOfWork e inyecta una clase IDataMapper específica de O / RM en ella.
  3. El NorthwindUnitOfWork es de hecho un contenedor de seguridad de tipos alrededor de IDataMapper y NorthwindUnitOfWork solicita IDataMapper para repositorios y reenvía solicitudes para enviar cambios y desecharlos al mapeador.
  4. IDataMapper devuelve Repository<T> clases Repository<T> y un repositorio implementa IQueryable<T> para permitir que el cliente use LINQ sobre el repositorio.
  5. La implementación específica de IDataMapper contiene una referencia a la unidad de trabajo específica O / RM (por ejemplo, ObjectContext de EF). Por esa razón, IDataMapper debe implementar IDisposable .

Esto da como resultado el siguiente diseño:

public interface INorthwindUnitOfWorkFactory { NorthwindUnitOfWork CreateNew(); } public interface IDataMapper : IDisposable { Repository<T> GetRepository<T>() where T : class; void Save(); } public abstract class Repository<T> : IQueryable<T> where T : class { private readonly IQueryable<T> query; protected Repository(IQueryable<T> query) { this.query = query; } public abstract void InsertOnSubmit(T entity); public abstract void DeleteOnSubmit(T entity); // IQueryable<T> members omitted. }

El NorthwindUnitOfWork es una clase concreta que contiene propiedades para repositorios específicos, como Customers , Orders , etc.

public sealed class NorthwindUnitOfWork : IDisposable { private readonly IDataMapper mapper; public NorthwindUnitOfWork(IDataMapper mapper) { this.mapper = mapper; } // Repository properties here: public Repository<Customer> Customers { get { return this.mapper.GetRepository<Customer>(); } } public void Dispose() { this.mapper.Dispose(); } }

Lo que queda es una implementación concreta de INorthwindUnitOfWorkFactory y una implementación concreta de IDataMapper . Aquí hay uno para Entity Framework:

public class EntityFrameworkNorthwindUnitOfWorkFactory : INorthwindUnitOfWorkFactory { public NorthwindUnitOfWork CreateNew() { var db = new ObjectContext("name=NorthwindEntities"); db.DefaultContainerName = "NorthwindEntities"; var mapper = new EntityFrameworkDataMapper(db); return new NorthwindUnitOfWork(mapper); } }

Y el EntityFrameworkDataMapper :

public sealed class EntityFrameworkDataMapper : IDataMapper { private readonly ObjectContext context; public EntityFrameworkDataMapper(ObjectContext context) { this.context = context; } public void Save() { this.context.SaveChanges(); } public void Dispose() { this.context.Dispose(); } public Repository<T> GetRepository<T>() where T : class { string setName = this.GetEntitySetName<T>(); var query = this.context.CreateQuery<T>(setName); return new EntityRepository<T>(query, setName); } private string GetEntitySetName<T>() { EntityContainer container = this.context.MetadataWorkspace.GetEntityContainer( this.context.DefaultContainerName, DataSpace.CSpace); return ( from item in container.BaseEntitySets where item.ElementType.Name == typeof(T).Name select item.Name).First(); } private sealed class EntityRepository<T> : Repository<T> where T : class { private readonly ObjectQuery<T> query; private readonly string entitySetName; public EntityRepository(ObjectQuery<T> query, string entitySetName) : base(query) { this.query = query; this.entitySetName = entitySetName; } public override void InsertOnSubmit(T entity) { this.query.Context.AddObject(entitySetName, entity); } public override void DeleteOnSubmit(T entity) { this.query.Context.DeleteObject(entity); } } }

Puede encontrar más información sobre este modelo here .

ACTUALIZACIÓN Diciembre 2012

Esta es una actualización escrita dos años después de mi respuesta original. Los últimos dos años han cambiado mucho en la forma en que trato de diseñar los sistemas en los que estoy trabajando. Aunque me ha quedado en el pasado, ya no me gusta usar el enfoque de fábrica cuando trato con el patrón de Unidad de trabajo. En su lugar, simplemente inyecté una instancia de unidad de trabajo directamente a los consumidores. Sin embargo, si este diseño es factible para usted, depende mucho de la forma en que esté diseñado su sistema. Si desea leer más sobre esto, eche un vistazo a esta respuesta más reciente de : One DbContext por solicitud web ... ¿por qué?