c# - unit - Unidad de trabajo con EF 6 y inyección de Dependencia Problemas de diseño
repository unit of work c# (3)
Parece que el problema no es realmente asegurarse de que la instancia de DbContext inyectada en UnitOfWork
y ProductsRepository
sea la misma.
Esto podría lograrse registrando DbContext como InstancePerLifetimeScope
y creando un nuevo LifetimeScope
antes de resolver IUnitOfWork
y ProductsRepository
. Cualquier dependencia que no sea suya será eliminada en el momento de la eliminación de LifetimeScope
.
El problema parece ser que no tienes una relación explícita entre esas dos clases. Su UoW no depende de ''ningún DbContext'', depende de cualquier DbContext que esté involucrado en la transacción actual. Ese específico.
Tampoco existe una relación directa entre tu UoW y los repositorios. Eso no parece un patrón de UoW .
No pude entender quién va a IRepository
el IRepository
creado por su IRepositoryFactory
. Está utilizando el contenedor para resolverlo (a través del ILifetimeScope
que se inyectó en RepositoriesFactory
). A menos que el que obtenga esa instancia de la Factory
elimine, solo se eliminará desechando el LifetimeScope
inyectado en IRepositoryFactory
.
Otro problema que podría surgir es la propiedad de DbContext. Podrías IUnitOfWork
en ese bloque using
Dispose en tu IUnitOfWork
. Pero tu UnitOfWork
tampoco posee esa instancia. El contenedor sí. ¿Los Repositorios también tratarían de eliminar el DbContext? También recibieron mediante inyección de constructor.
Sugeriría repensar esta solución.
Desarrollo una aplicación web con entidad marco 6, y tengo dificultades con el diseño de la estructura de la aplicación. Mi problema principal es cómo lidiar con la inyección de dependencia en mi caso específico.
El siguiente código es cómo me gustaría que se vea la aplicación. Estoy usando Autofac pero supongo que es lo suficientemente básico para que todos los usuarios de DI entiendan:
public interface IUnitOfWork
{
bool Commit();
}
public class UnitOfWork : IUnitOfWork, IDisposable
{
private DbContext _context;
public UnitOfWork(DbContext context)
{
_context = context;
}
public bool Commit()
{
// ..
}
public bool Dispose()
{
_context.Dispose();
}
}
public class ProductsController : ApiController
{
public ProductsController(IProductsManager managet)
}
public class ProductsManager : IProductsManager
{
private Func<Owned<IUnitOfWork>> _uowFactory;
private IProductsDataService _dataService;
public Manager(Func<Owned<IUnitOfWork>> uowFactory, IProductsDataService dataService)
{
_dataService = dataService;
_uowFactory = uowFactory;
}
public bool AddProduct(ProductEntity product)
{
using (ownedUow = _uowFactory())
{
var uow = ownedUow.Value;
var addedProduct = _dataService.AddProduct(product);
if (addedProduct != null)
uow.Commit();
}
}
}
public interface IProductsDataService
{
ProductEntity AddProduct (Product product)
}
public class ProductsDataService : IProductsDataService
{
private IRepositoriesFactory _reposFactory;
public DataService(IRepositoriesFactory reposFactory)
{
_reposFactory = reposFactory;
}
public ProductEntity AddProduct(ProductEntity product)
{
var repo = reposFactory.Get<IProductsRepository>();
return repo.AddProduct(product);
}
}
public interface IRepositoriesFactory
{
T Get<T>() where T : IRepository
}
public class RepositoriesFactory : IRepositoriesFactory
{
private ILifetimeScope _scope;
public RepositoriesFactory(ILifetimeScope scope)
{
_scope = scope;
}
public T Get<T>() where T : IRepository
{
return _scope.Resolve<T>();
}
}
public interface IProductsRepository
{
ProductEntity AddProduct(ProductEntity);
}
public ProductsRepository : IProductsRepository
{
private DbContext _context;
public ProductsRepository(DbContext context)
{
_context = context;
}
public ProductEntity AddProduct(ProductEntity)
{
// Implementation..
}
}
Esta es la implementación que considero ideal, sin embargo, no sé cómo lograr esto, ya que mi ProductsDataService es singleton, por lo tanto, no está relacionado con el alcance de propiedad que crea la unidad de la fábrica de trabajos. ¿Hay alguna manera de asociar los Repositorios que se crearán y tomar en su ctor el mismo DbContext que se creó para la unidad de trabajo? ¿Cambia el código en RepositoriesFactory de alguna manera?
Por el momento lo que tengo es que la unidad de trabajo contiene la fábrica de repositorios para que el contexto dentro de los repositorios sea el mismo que en la unidad de trabajo (registro el DbContext según el alcance), el gerente en este momento hace el trabajo del DataService también, que no me gusta.
Sé que puedo pasar la inyección del método UnitOfWork a los métodos de DataService, pero prefiero usar la inyección Ctor, ya que se ve mejor en mi opinión.
Lo que quiero es separar esto: un gerente que su trabajo es instanciar una unidad de obras y enviarlas si es necesario, y otra clase (DataService) que realmente ejecuta la lógica.
De todos modos, me gustaría escuchar su opinión sobre esta implementación si tiene algún comentario / idea de mejora.
¡Gracias por tu tiempo!
EDITAR: Esto es lo que terminé con:
public interface IUnitOfWork
{
bool Commit();
}
public class DatabaseUnitOfWork : IUnitOfWork
{
private DbContext _context;
public DatabaseUnitOfWork(DbContext context)
{
_context = context;
}
public bool Commit()
{
// ..
}
}
// Singleton
public class ProductsManager : IProductsManager
{
private Func<Owned<IProductsDataService>> _uowFactory;
public ProductsManager(Func<Owned<IProductsDataService>> uowFactory)
{
_uowFactory = uowFactory;
}
public bool AddProduct(ProductEntity product)
{
using (ownedUow = _uowFactory())
{
var dataService = ownedUow.Value;
var addedProduct = _dataService.AddProduct(product);
if (addedProduct != null)
uow.Commit();
}
}
}
public interface IProductsDataService : IUnitOfWork
{
ProductEntity AddProduct (Product product)
}
public class ProductsDataService : DatabaseUnitOfWork, IDataService
{
private IRepositoriesFactory _reposFactory;
public DataService(IRepositoriesFactory reposFactory)
{
_reposFactory = reposFactory;
}
public ProductEntity AddProduct(ProductEntity product)
{
var repo = _reposFactory .Get<IProductsRepository>();
return repo.AddProduct(product);
}
}
public interface IRepositoriesFactory
{
Get<T>() where T : IRepository
}
public class RepositoriesFactory : IRepositoriesFactory
{
private ILifetimeScope _scope;
public RepositoriesFactory(ILifetimeScope scope)
{
_scope = scope;
}
public Get<T>() where T : IRepository
{
return _scope.Resolve<T>();
}
}
public interface IProductsRepository
{
ProductEntity AddProduct(ProductEntity);
}
public ProductsRepository : IProductsRepository
{
private DbContext _context;
public ProductsRepository(DbContext context)
{
_context = context;
}
public ProductEntity AddProduct(ProductEntity)
{
// Implementation..
}
}
Estoy de acuerdo con el consejo de Bruno Garcia sobre el problema con su código. Sin embargo, veo algunos otros problemas con eso.
Comenzaré diciendo que no he usado el patrón de unidad de trabajo explícitamente como lo hizo, pero entiendo para qué va.
El problema en el que Bruno no se metió es el hecho de que tiene una pobre separación de preocupaciones. Hizo una alusión a eso un poco, y voy a explicar más: su controlador tiene dos objetos que compiten por separado, ambos tratando de utilizar el mismo recurso (DbContext). Como dijo, lo que estás buscando hacer es tener solo un DbContext para cada solicitud. Sin embargo, hay un problema con eso: no hay nada que impida que un Controlador intente continuar utilizando el Depósito de Productos después de deshacerse de UnitOfWork. Si lo hace, la conexión a la base de datos ya se ha eliminado.
Como tiene dos objetos que necesitan usar el mismo recurso, debe volver a crearlo donde un objeto encapsula al otro. Esto también brinda el beneficio adicional de ocultarle al Controlador cualquier inquietud acerca de la Propagación de Datos.
Todo lo que debe saber el controlador es su objeto de servicio, que debe contener toda la lógica de negocios, así como las puertas de enlace al repositorio y su unidad de trabajo, mientras lo mantiene invisible para el consumidor del servicio. De esta forma, el Controlador solo tiene un único objeto del que preocuparse por el manejo y la eliminación.
Una de las otras formas de evitar esto sería hacer que ProductsRepository se derive de UnitOfWork para que no tenga que preocuparse por ningún código duplicado.
Luego, dentro de su método AddProduct
, debe llamar a _context.SaveChanges()
antes de devolver ese objeto a lo largo de la tubería a su Controller.
ACTUALIZACIÓN (Los apoyos están diseñados para compacidad)
Aquí está el diseño de lo que te gustaría hacer:
UnitOfWork es la capa inferior que incluye la conexión a la base de datos. Sin embargo, haga este abstract
ya que no desea permitir una implementación concreta del mismo. Ya no necesita la interfaz, ya que lo que hace dentro de su método Commit
nunca debe estar expuesto, y el guardado del objeto debe hacerse dentro de los métodos. Mostraré cómo hacerlo en la línea.
public abstract class UnitOfWork : IDisposable {
private DbContext _context;
public UnitOfWork(DbContext context) {
_context = context;
}
protected bool Commit() {
// ... (Assuming this is calling _context.SaveChanges())
}
public bool Dispose() {
_context.Dispose();
}
}
Tu repositorio es la siguiente capa. Derive eso de UnitOfWork
para que herede todo el comportamiento, y será el mismo para cada uno de los tipos específicos.
public interface IProductsRepository {
ProductEntity AddProduct(ProductEntity product);
}
public ProductsRepository: UnitOfWork, IProductsRepository {
public ProductsRepository(DbContext context) : base(context) { }
public ProductEntity AddProduct(ProductEntity product) {
// Don''t forget to check here. Only do that where you''re using it.
if (product == null) {
throw new ArgumentNullException(nameof(product));
}
var newProduct = // Implementation...
if (newProduct != null) {
Commit();
}
return newProduct;
}
}
Con eso en su lugar, todo lo que importa ahora es simplemente tener su ProductsRepository. En su capa DataService, utilice Dependency Injection y simplemente pase el ProductsRepository. Si realmente está listo para usar la fábrica, pase la fábrica, pero tenga su variable miembro todavía sea el IProductsRepository
. No hagas que cada método tenga que resolverlo.
No olvides que todas tus interfaces derivan de IDisposable
public interface IProductsDataService : IDisposable {
ProductEntity AddProduct(ProductEntity product);
}
public class ProductsDataService : IProductsDataService {
private IProductsRepository _repository;
public ProductsDataService(IProductsRepository repository) {
_repository = repository;
}
public ProductEntity AddProduct(ProductEntity product) {
return _repository.AddProduct(product);
}
public bool Dispose() {
_repository.Dispose();
}
}
Si hubiera decidido usar el ProductsManager
, puede hacerlo, pero es solo otra capa que ya no proporciona mucho beneficio. El mismo trato iría con esa clase.
Terminaré con tu Controlador como lo haría.
public class ProductsController : Controller {
private IProductsDataService _service;
public ProductsController(IProductsDataService service) {
_service = service;
}
protected override void Dispose(bool disposing) {
_service.Dispose();
base.Dispose(disposing);
}
// Or whatever you''re using it as.
[HttpPost]
public ActionResult AddProduct(ProductEntity product) {
var newProduct = _service.AddProduct(product);
return View(newProduct);
}
}
No desea DbContext
singleton en una instancia singleton. Esto está bien, se puede hacer con la fábrica. Además, desea compartir este DbContext
. Esto también está bien, puede resolver y devolver DbContext
con el tiempo de vida relacionado en la fábrica. El problema es ; desea compartir DbContext
no singleton en una sola instancia sin tiempo de vida de administración (solicitud de Tcp / Ip).
¿Cuál es la razón por la cual ProductService
y ProductManager
son singleton? Sugiero que use ProductService
y ProductManager
por lifetimescope. Cuando tienes una solicitud http está bien. Cuando tenga una solicitud de tcp / ip, puede comenzar un nuevo alcance de por vida (lo más alto posible) y luego resolver ProductManager
allí.
Actualización : Código para la solución 1 que mencioné en los comentarios.
Managers
deben ser solteros (como usted dijo).
Además de los managers
, debe registrar dbcontext
, services
, repositories
y Uow
según per lifetime
alcance de la per lifetime
.
Podríamos iniciarnos así:
public class ProductsManager : IProductsManager
{
//This is kind of service locator. We hide Uow and ProductDataService dependencies.
private readonly ILifetimeScope _lifetimeScope;
public ProductsManager(ILifetimeScope lifetimeScope)
{
_lifetimeScope = lifetimeScope;
}
}
Pero este es un tipo de localizador de servicios. ProductDataService
dependencias de Uow
y ProductDataService
.
Entonces, debemos implementar un proveedor:
public IProductsManagerProvider : IProductsManager
{
}
public class ProductsManagerProvider : IProductsManagerProvider
{
private readonly IUnitOfWork _uow;
private readonly IProductsDataService _dataService;
public ProductsManagerProvider (IUnitOfWork uow, IProductsDataService dataService)
{
_dataService = dataService;
_uow = uow;
}
public bool AddProduct(ProductEntity product)
{
var result=false;
var addedProduct = _dataService.AddProduct(product);
if (addedProduct != null)
result=_uow.Commit()>0;
return result;
}
}
Y lo registramos según la per dependency
(Porque lo usaremos con la fábrica).
container.RegisterType<ProductsManagerProvider>().As<IProductsManagerProvider>().InstancePerDependency();
Su clase de ProductsManager
debería ser así. (Ahora no ocultamos ninguna de las dependencias).
public class ProductsManager : IProductsManager
{
private readonly Func<Owned<IProductsManagerProvider>> _managerProvider;
//Now we don''t hide any dependencies.
public ProductsManager(Func<Owned<IProductsManagerProvider>> managerProvider)
{
_managerProvider = managerProvider;
}
public bool AddProduct(ProductEntity product)
{
using (var provider = _managerProvider())
{
return provider.Value.AddProduct(product);
}
}
}
He probado con mis propias clases.
Tiene instancia de singleton manager que tiene una fábrica para crear el administrador de proveedor. Los proveedores de administrador son por dependencia porque cada vez deberíamos obtener una nueva instancia en singleton. Todo en proveedores de por vida, por lo que su vida útil son proveedores conectados por tiempo de vida de dependencia.
Cuando agrega un producto en el administrador, Container
crea 1 Provider
, 1 DbContext
, 1 DataService
y 1 Uow
( DbContext
es compartido). Provider
está dispuesto (por dependencia) con todas las instancias realegadas (DbContex, Uow, DataService) después del método de devoluciones en el Manager
.