none
Unit Of Work para diferentes DbContext RRS feed

  • Pregunta

  • Buenas,

    Tengo la siguiente interfaz para implementar el patrón Unidad de Trabajo:

    public interface IUnitOfWork : IDisposable
    {
    	int Save();
    
    	Task<int> SaveAsync();
    }

    Su implementación:

    public sealed class UnitOfWork : IUnitOfWork
    {
    	private DbContext context;
    
    	public UnitOfWork(DbContext context)
    	{
    		this.context = context;
    	}
    
    	public int Save()
    	{
    		return context.SaveChanges();
    	}
    
    	public Task<int> SaveAsync()
    	{
    		return context.SaveChangesAsync();
    	}
    
    	public void Dispose()
    	{
    		try
    		{
    			if (context != null)
    			{
    				context.Dispose();
    				GC.SuppressFinalize(this);
    			}
    		}
    		catch (Exception ex)
    		{
    			throw ex;
    		}
    	}
    	
    	private void Dispose(bool disposing)
    	{
    		if (disposing)
    		{
    			if (context != null)
    			{
    				context.Dispose();
    				context = null;
    			}
    		}
    	}
    }

    En IoC tengo:

    container.Register<IUnitOfWork>(() => new UnitOfWork(container.GetInstance<MyDbContext_01>()), Lifestyle.Scoped);

    Esto me funciona correctamente para repositorios cuando trabajan con MyDbContext_01 pero quisiera poder trabajar UnitOfWork con otros contextos.

    lunes, 7 de enero de 2019 16:45

Respuestas

  • Aunque no lo he probado bien, puedes establecer registros condicionales y establecer que dado un attribute resuelva contra una clase especifica.

    Por ejemplo registramos así. Estableciendo un PredicateContext indicando que  el valor de un attribute personlizado es de un valor determinado

               
                var container = new Container();
                container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
    
                
                container.Register<ApplicationDbContext, ApplicationDbContext>(Lifestyle.Scoped);
                container.Register<ApplicationDbContext2, ApplicationDbContext2>(Lifestyle.Scoped);
    
    
                var f1 = Lifestyle.Scoped.CreateRegistration<IGenericUoW>(
                                () => new GenericUoW(container.GetInstance<ApplicationDbContext>()),
                                 container);
    
                var f2 = Lifestyle.Scoped.CreateRegistration<IGenericUoW>(
                                () => new GenericUoW(container.GetInstance<ApplicationDbContext2>()),
                                 container);
    
                container.RegisterConditional(typeof(IGenericUoW), f1, InjectedInto<GenericUoW>);
                container.RegisterConditional(typeof(IGenericUoW), f2, InjectedInto2<GenericUoW>);
    
              
                container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
    
                // Helper method.
                container.Verify();
    
                DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
            }
    
            // Helper method.
            static bool InjectedInto<T>(PredicateContext c) =>  c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MyDbContext_01";
            static bool InjectedInto2<T>(PredicateContext c) => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MyDbContext_02";



    Implementamos un attribute personalizado

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]

    public class NamedAttribute : System.Attribute { public NamedAttribute(string name) { this.Name = name; } public string Name { get; private set; } }

    y luego establecer el atributeo


    public class EvaluacionSolicitudDomainService : GenericService<OMP_EvaluacionSolicitud>, IEvaluacionSolicitudService { private readonly IUnitOfWork unitOfWork; private readonly ISolicitudRepository solicitudRepository; private readonly IEvaluacionSolicitudRepository evaluacionSolicitudRepository; public EvaluacionSolicitudDomainService([Named("MyDbContext_01")] IUnitOfWork unitOfWork, ISolicitudRepository solicitudRepository, IEvaluacionSolicitudRepository evaluacionSolicitudRepository) : base(unitOfWork, evaluacionSolicitudRepository) { this.unitOfWork = unitOfWork; this.solicitudRepository = solicitudRepository; this.evaluacionSolicitudRepository = evaluacionSolicitudRepository; } public async Task<int> CrearEvaluacionAsync(OMP_Solicitud solicitud, OMP_EvaluacionSolicitud evaluacionSolicitud) { // Actualizar la solicitud solicitudRepository.Update(solicitud); // ... // Actualizar evaluación evaluacionSolicitudRepository.Add(evaluacionSolicitud); // Confirmar transacción return await unitOfWork.SaveAsync(); } }



    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos




    martes, 8 de enero de 2019 15:32
    Moderador
  • Después de tanto lidiar pude resolver el problema,

    la entidad User y UserProfileInfo están relacionadas de 1 a 1 y basta con eliminar la entidad User para que automáticamente se elimine la otra y no era necesario eliminarla explícitamente.

    Cabe mencionar que debo activar las opciones LazyLoadingEnabled y ProxyCreationEnabled en la Configuración del Contexto.

    El método Eliminar en el repositorio genérico quedó de la siguiente manera:

    public virtual void Delete(TEntity item)
    {
    	try
    	{
    		context.Set<TEntity>().Attach(item);
    		//context.Entry(item).State = EntityState.Deleted;
    		context.Set<TEntity>().Remove(item);
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    Muchas gracias, Saludos.


    viernes, 11 de enero de 2019 16:43

Todas las respuestas

  • hola

    el teme es como utlizas la inyeccion, porque se puede resolver por nombre, pero no mencionas que librerias de IoC utilizas

    por ejempli usando Unity se puede hacer

    Register Named Type

    entonces usan

    container.RegisterType<ICar, BMW>(); container.RegisterType<ICar, Audi>("LuxuryCar");

    y resuelven usando

    ICar bmw = container.Resolve<ICar>(); ICar audi = container.Resolve<ICar>("LuxuryCar");

    podrias aplicar la misma tecnica usando la misma interfaz pero indicando el nombre cuando inyecta

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina




    lunes, 7 de enero de 2019 17:11
  • Hola Leandro,

    Lo inyecto por constructor en las clases de Servicios de Dominio.

    public class EvaluacionSolicitudDomainService : GenericService<OMP_EvaluacionSolicitud>, IEvaluacionSolicitudService
    {
    	private readonly IUnitOfWork unitOfWork;
    	private readonly ISolicitudRepository solicitudRepository;
    	private readonly IEvaluacionSolicitudRepository evaluacionSolicitudRepository;
    
    	public EvaluacionSolicitudDomainService(IUnitOfWork unitOfWork,
    						ISolicitudRepository solicitudRepository,
    						IEvaluacionSolicitudRepository evaluacionSolicitudRepository) :
    		base(unitOfWork, evaluacionSolicitudRepository)
    	{
    		this.unitOfWork = unitOfWork;
    		this.solicitudRepository = solicitudRepository;
    		this.evaluacionSolicitudRepository = evaluacionSolicitudRepository;
    	}
    
    	public async Task<int> CrearEvaluacionAsync(OMP_Solicitud solicitud, OMP_EvaluacionSolicitud evaluacionSolicitud)
    	{
    		// Actualizar la solicitud
    		solicitudRepository.Update(solicitud);
    
    		// ...
    		
    		// Actualizar evaluación
    		evaluacionSolicitudRepository.Add(evaluacionSolicitud);
    
    		// Confirmar transacción
    		return await unitOfWork.SaveAsync();
    	}
    }

    Estoy utilizando SimpleInjector como contenedor IoC, imagino que debe haber un equivalente del método RegisterType

    lunes, 7 de enero de 2019 20:15
  • Aunque no lo he probado bien, puedes establecer registros condicionales y establecer que dado un attribute resuelva contra una clase especifica.

    Por ejemplo registramos así. Estableciendo un PredicateContext indicando que  el valor de un attribute personlizado es de un valor determinado

               
                var container = new Container();
                container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
    
                
                container.Register<ApplicationDbContext, ApplicationDbContext>(Lifestyle.Scoped);
                container.Register<ApplicationDbContext2, ApplicationDbContext2>(Lifestyle.Scoped);
    
    
                var f1 = Lifestyle.Scoped.CreateRegistration<IGenericUoW>(
                                () => new GenericUoW(container.GetInstance<ApplicationDbContext>()),
                                 container);
    
                var f2 = Lifestyle.Scoped.CreateRegistration<IGenericUoW>(
                                () => new GenericUoW(container.GetInstance<ApplicationDbContext2>()),
                                 container);
    
                container.RegisterConditional(typeof(IGenericUoW), f1, InjectedInto<GenericUoW>);
                container.RegisterConditional(typeof(IGenericUoW), f2, InjectedInto2<GenericUoW>);
    
              
                container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
    
                // Helper method.
                container.Verify();
    
                DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
            }
    
            // Helper method.
            static bool InjectedInto<T>(PredicateContext c) =>  c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MyDbContext_01";
            static bool InjectedInto2<T>(PredicateContext c) => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MyDbContext_02";



    Implementamos un attribute personalizado

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]

    public class NamedAttribute : System.Attribute { public NamedAttribute(string name) { this.Name = name; } public string Name { get; private set; } }

    y luego establecer el atributeo


    public class EvaluacionSolicitudDomainService : GenericService<OMP_EvaluacionSolicitud>, IEvaluacionSolicitudService { private readonly IUnitOfWork unitOfWork; private readonly ISolicitudRepository solicitudRepository; private readonly IEvaluacionSolicitudRepository evaluacionSolicitudRepository; public EvaluacionSolicitudDomainService([Named("MyDbContext_01")] IUnitOfWork unitOfWork, ISolicitudRepository solicitudRepository, IEvaluacionSolicitudRepository evaluacionSolicitudRepository) : base(unitOfWork, evaluacionSolicitudRepository) { this.unitOfWork = unitOfWork; this.solicitudRepository = solicitudRepository; this.evaluacionSolicitudRepository = evaluacionSolicitudRepository; } public async Task<int> CrearEvaluacionAsync(OMP_Solicitud solicitud, OMP_EvaluacionSolicitud evaluacionSolicitud) { // Actualizar la solicitud solicitudRepository.Update(solicitud); // ... // Actualizar evaluación evaluacionSolicitudRepository.Add(evaluacionSolicitud); // Confirmar transacción return await unitOfWork.SaveAsync(); } }



    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos




    martes, 8 de enero de 2019 15:32
    Moderador
  • EDITO Mi respuesta anterior con unas correcciones

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    martes, 8 de enero de 2019 22:18
    Moderador
  • Hola Sergio,

    Me está lanzando la siguiente excepción al ejecutarse la línea container.Verify();

    Multiple applicable registrations found for IUnitOfWork. The applicable registrations are (1) the conditional registration for IUnitOfWork using IUnitOfWork and (2) the conditional registration for IUnitOfWork using IUnitOfWork. If your goal is to make one registration a fallback in case another registration is not applicable, make the fallback registration last using RegisterConditional and make sure the supplied predicate returns false in case the Handled property is true.

    miércoles, 9 de enero de 2019 12:28
  • Hola, puedes mostrar el código completo de la parte del registro? En un ejemplo que implementé ayer no me mostraba esta excepción...

    Tal vez agregando

     && !c.Handled

    en

     static bool InjectedInto<T>(PredicateContext c) =>  c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MyDbContext_01" && !c.Handled ;
      static bool InjectedInto2<T>(PredicateContext c) => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MyDbContext_02" && !c.Handled;



    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos


    miércoles, 9 de enero de 2019 12:46
    Moderador
  • En la capa de Presentación tengo el Inicializador:

    public static void Initialize()
    {
    	try
    	{
    		var container = new Container();
    		container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
    
    		Infraestructure.CrossCutting.IoC.BootStrapper.RegisterServices(container);
    
    		container.Register(() =>
    		{
    			if (HttpContext.Current != null && HttpContext.Current.Items["owin.Environment"] == null && container.IsVerifying)
    				return new OwinContext().Authentication;
    
    			return HttpContext.Current.GetOwinContext().Authentication;
    		}, Lifestyle.Scoped
    		);
    
    		container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
    
    		container.Verify();
    
    		DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    En la llamada a IoC tengo:

    private static void RegisterServices(Container container) { try { container.Register(() => new IdentityContext(autodetectChangesEnabled: false, lazyLoadingEnabled: false, proxyCreationEnabled: false), Lifestyle.Scoped); container.Register(() => new HapuyoContext(autodetectChangesEnabled: false, lazyLoadingEnabled: false, proxyCreationEnabled: false), Lifestyle.Scoped); var f1 = Lifestyle.Scoped.CreateRegistration<IUnitOfWork>(() => new UnitOfWork(container.GetInstance<IdentityContext>()), container); var f2 = Lifestyle.Scoped.CreateRegistration<IUnitOfWork>(() => new UnitOfWork(container.GetInstance<HapuyoContext>()), container); container.RegisterConditional(typeof(IUnitOfWork), f1, InjectedInto<UnitOfWork>); container.RegisterConditional(typeof(IUnitOfWork), f2, InjectedInto<UnitOfWork>); container.Register(typeof(IGenericRepository<>), typeof(GenericRepository<>));

    // ... } catch (Exception ex) { throw ex; } } // Helper method. static bool InjectedInto<T>(PredicateContext c) => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "IdentityDbContext"; static bool InjectedInto2<T>(PredicateContext c) => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "HapuyoDbContext";

    En el servicio donde se inyectan:

    public class EvaluacionSolicitudService : GenericService<OMP_EvaluacionSolicitud>, IEvaluacionSolicitudService
    {
    	private readonly IUnitOfWork unitOfWork;
    	private readonly ISolicitudRepository solicitudRepository;
    	private readonly IEvaluacionSolicitudRepository evaluacionSolicitudRepository;
    
    	public EvaluacionSolicitudService([Named("HapuyoDbContext")]IUnitOfWork unitOfWork,
    									  ISolicitudRepository solicitudRepository,
    									  IEvaluacionSolicitudRepository evaluacionSolicitudRepository) :
    		base(unitOfWork, evaluacionSolicitudRepository)
    	{
    		this.unitOfWork = unitOfWork;
    		this.solicitudRepository = solicitudRepository;
    		this.evaluacionSolicitudRepository = evaluacionSolicitudRepository;
    	}
    
    	public async Task<int> CrearEvaluacionAsync(OMP_Solicitud solicitud, OMP_EvaluacionSolicitud evaluacionSolicitud)
    	{
    		// Actualizar la solicitud
    		solicitudRepository.Update(solicitud);
    
    		// ...
    
    		// Actualizar evaluación
    		evaluacionSolicitudRepository.Add(evaluacionSolicitud);
    
    		// Confirmar transacción
    		return await unitOfWork.SaveAsync();
    	}
    }


    • Editado eduar2083 miércoles, 9 de enero de 2019 12:58
    miércoles, 9 de enero de 2019 12:56
  • El error está aqui, estas usando el mismo predicado InjectedInto

    container.RegisterConditional(typeof(IUnitOfWork), f1, InjectedInto<UnitOfWork>);

    container.RegisterConditional(typeof(IUnitOfWork), f2, InjectedInto<UnitOfWork>); <---- AQUÍ DEBES USAR InjectedInto2



    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos


    miércoles, 9 de enero de 2019 14:09
    Moderador
  • Gracias Sergio,

    Con ese cambio ya funciona de manera transaccional con HapuyoDbContext, sin embargo, me está lanzando excepción al realizar una transacción sobre IdentityDbContext. El método a ejecutar es el siguiente:

    public class UserService : GenericService<User>, IUserService
    {
    	private readonly IUnitOfWork unitOfWork;
    	private readonly IUserRepository userRepository;
    	private readonly IPasswordHistoryRepository passwordHistoryRepository;
    	private readonly IUserProfileInfoRepository userProfileInfoRepository;
    
    	public UserService([Named("IdentityDbContext")]IUnitOfWork unitOfWork,
    					   IUserRepository userRepository,
    					   IPasswordHistoryRepository passwordHistoryRepository,
    					   IUserProfileInfoRepository userProfileInfoRepository) : base(unitOfWork, userRepository)
    	{
    		this.unitOfWork = unitOfWork;
    		this.userRepository = userRepository;
    		this.userProfileInfoRepository = userProfileInfoRepository;
    		this.passwordHistoryRepository = passwordHistoryRepository;
    	}
    	
    	public int Eliminar(User user)
    	{
    		try
    		{
    			PasswordHistory ph = passwordHistoryRepository.GetBySpecification(new PasswordHistorySpecification(user.UserProfileInfo.Id));
    			if (ph != null)
    			{
    				passwordHistoryRepository.Delete(ph);
    			}
    
    			UserProfileInfo upi = userProfileInfoRepository.GetBySpecification(new UserProfileInfoSpecification(user.UserProfileInfo.Id));
    			if (upi != null)
    			{
    				userProfileInfoRepository.Delete(upi);
    			}
    
    			base.Delete(user);
    
    			return unitOfWork.Save();
    		}
    		catch (Exception ex)
    		{
    			throw ex;
    		}
    	}
    }

    La excepción ocurre al ejecutar la línea userProfileInfoRepository.Delete(upi); y el error el el siguiente:

    Attaching an entity of type 'Infraestructure.CrossCutting.Identity.Models.UserProfileInfo' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

    El método Delete del repositorio genérico tiene la siguiente forma:

    public virtual void Delete(TEntity item)
    {
    	try
    	{
    		context.Set<TEntity>().Attach(item);
    		context.Entry(item).State = EntityState.Deleted;
    		context.Set<TEntity>().Remove(item);
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    No entiendo muy bien a qué se debe.


    • Editado eduar2083 miércoles, 9 de enero de 2019 19:57
    miércoles, 9 de enero de 2019 19:55
  • Hola, si se usa el mismo contexto creo que no hace falta hacer el Attach(item) .comenta esa linea

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    miércoles, 9 de enero de 2019 23:47
    Moderador
  • Le he comentado pero el problema persiste:

    public virtual void Delete(TEntity item)
    {
    	try
    	{
    		//context.Set<TEntity>().Attach(item);
    		context.Entry(item).State = EntityState.Deleted;
    		context.Set<TEntity>().Remove(item);
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    jueves, 10 de enero de 2019 0:18
  • Y marcar solo como Deleted y hacer el SaveChanges vía UoW?

    Solo tienes este problema con el. Context? 


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    jueves, 10 de enero de 2019 0:28
    Moderador
  • He probado sólo marcando como Deleted

    public virtual void Delete(TEntity item)
    {
    	try
    	{
    		//context.Set<TEntity>().Attach(item);
    		context.Entry(item).State = EntityState.Deleted;
    		//context.Set<TEntity>().Remove(item);
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    Pero aún lanza la excepción:

    Attaching an entity of type 'Infraestructure.CrossCutting.Identity.Models.UserProfileInfo' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

    Probé comentando también esa línea:

    public virtual void Delete(TEntity item)
    {
    	try
    	{
    		//context.Set<TEntity>().Attach(item);
    		//context.Entry(item).State = EntityState.Deleted;
    		context.Set<TEntity>().Remove(item);
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    Pero ahora se cae en la primera llamada al Delete passwordHistoryRepository.Delete(ph);

    The object cannot be deleted because it was not found in the ObjectStateManager.

    El problema es sólo con ese contexto.


    • Editado eduar2083 jueves, 10 de enero de 2019 0:46
    jueves, 10 de enero de 2019 0:45
  • Después de tanto lidiar pude resolver el problema,

    la entidad User y UserProfileInfo están relacionadas de 1 a 1 y basta con eliminar la entidad User para que automáticamente se elimine la otra y no era necesario eliminarla explícitamente.

    Cabe mencionar que debo activar las opciones LazyLoadingEnabled y ProxyCreationEnabled en la Configuración del Contexto.

    El método Eliminar en el repositorio genérico quedó de la siguiente manera:

    public virtual void Delete(TEntity item)
    {
    	try
    	{
    		context.Set<TEntity>().Attach(item);
    		//context.Entry(item).State = EntityState.Deleted;
    		context.Set<TEntity>().Remove(item);
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    Muchas gracias, Saludos.


    viernes, 11 de enero de 2019 16:43