3

Im trying to implement Unit of Work with Autofac and Mediatr. Here how is the flow

architecture

but i couldn't make Autofac to send same instance of Unit OfWork (which takes DbContext as parameter) inside a scope. I want to execute that whole scope inside a single transaction, that means when i get to the point processHandler it should create a instance of DbContext and share the same instance into nested handlers. such that i can create a transaction on processhandler level and share the same transaction to nested handlers.

here is my DI setup

 builder.Register(ctx =>
        {
            var contextSvc = ctx.Resolve<IContextService>(); // owin context 
            var connBuilder = ctx.Resolve<IDbConnectionBuilder>(); 
            return SapCommandDb.Create(contextSvc.GetMillCode(), connBuilder.BuildConnectionAsync(IntegrationConnectionName, contextSvc.GetMillCode()).Result);
        }).AsSelf().InstancePerLifetimeScope();

        builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IDomainRepository<>)).InstancePerLifetimeScope();
        builder.RegisterType<EFUnitOfWork>().As<IEFUnitOfWork>().InstancePerLifetimeScope();



 public class ProcessHandler : AsyncRequestHandler<IntermediateDocument.Command>
    {
        IMediator _mediator;
        Func<Owned<IEFUnitOfWork>> _uow;
        ILifetimeScope _scope;
        public ProcessHandler(
            ILifetimeScope scope,
            Func<Owned<IEFUnitOfWork>> uow,
            IMediator mediator)
        {
            _mediator = mediator;
            _scope = scope;
            _uow = uow;
        }
        protected async override Task Handle(Command request, CancellationToken cancellationToken)
        {
            foreach (var transaction in request.Transactions)
            {
                using (var scope = _scope.BeginLifetimeScope("custom"))
                {
                    using (var uo = _uow())
                    {
                        await uo.Value.Execute(async () =>
                        {
                            await _mediator.Send(new NestedHandlerGetBySwitch.Command(transaction));
                        });
                    }
                }
            }
        }
    }

the above one is the process handler

public class NestedHandler1 : AsyncRequestHandler<NestedHandler.Command>
        {
            IMediator _mediator;
            IEFUnitOfWork _uow;
            public NestedHandler1(
                IEFUnitOfWork uow,
                IMediator mediator)
            {
                _mediator = mediator;
                _uow = uow;
            }
            protected async override Task Handle(Command request, CancellationToken cancellationToken)
            {
                _uow.Repository.Add(request);
            }
        }

the above one is an example of nested handler. I want the same _uow instance from processhandler.

EFUNitOFWork looks like

public class EfUnitOfWork : IEFUnitOfWork {
    private DbContext _context;
    ABCRepository aBCRepository;
    public ABCRepository ABCRepository { get {
            return aBCRepository = aBCRepository ?? new ABCRepository(_context);
        } }
    public EfUnitOfWork(DbContext context)
    {
        _context = context;
    }

    public Task Add(Entity entity) {
        await _context.AddAsync(entity);
    }
}

what am i doing wrong ?

Thankyou.

MustangManiac
  • 317
  • 2
  • 13
  • im sorry. can you please check the edited post. Thanks – MustangManiac Sep 17 '18 at 22:12
  • 1
    The design seems complicated. You don't need an additional UOW because DbContext is in fact your UOW. For the registration, you may want to use a factory pattern to register the handlers. Take a look here: https://stackoverflow.com/questions/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core –  Oct 04 '18 at 20:22

2 Answers2

0

IMediator is asking AutoFac to create an instance of NestedHandler1, so it is going to have the same lifetime scope as IMediator.

One way of solving it is to resolve IMediator from the "custom" lifetime scope and use that one instead of injecting it in the constructor, and make sure that the UnitOfWork is properly registered in this scope:

using (var uo = _uow())
{
    using (var scope = _scope.BeginLifetimeScope("custom", x => x.RegisterInstance(uo.Value))
    {
        var mediator = scope.Resolve<IMediator>();

        await uo.Value.Execute(async () =>
        {
            await mediator.Send(new NestedHandlerGetBySwitch.Command(transaction));
        });
    }
}
Jeroen
  • 1,212
  • 10
  • 24
0

You have a bit of a mess between UnitsOfWork, Mediators and stuff.

Let's keep things simple and deduce the implementation from the requirements.

  1. You need to have a single DbContext shared by multiple components.
  2. A single request could process multiple operations, by multiple handlers.

Given this two facts, we can infer that we need two distinct lifetime scopes: the first to share the DbContext (we will call this "UnitOfWork"), and the second that corresponds to each and every operation (let's call this "Operation").

The handling of this structure will be handled like so:

public class ProcessHandler : AsyncRequestHandler<IntermediateDocument.Command>
{
    // ...ctor 

    protected async override Task Handle(Command request, CancellationToken cancellationToken)
    {
        // inside this using every component asking for an
        // IEFUnitOfWork will get the same instance 
        using (var unitOfWorkScope = _scope.BeginLifetimeScope("UnitOfWork"))
        {
            foreach (var transaction in request.Transactions)
            {
                // we don't need this inner scope to be tagged, AFAICT
                // so probably "Operation" could be omitted.
                using (var scope = unitOfWorkScope.BeginLifetimeScope("Operation"))
                {
                    // I'm not sure what a "mediator" is, I'm just copying the example code
                    var mediator = scope.Resolve<IMediator>();

                    await mediator.Send(...do something with transaction);
                } // here mediator will be disposed, once for each transaction instance
            }
        } // here everything resolved inside unitOfWorkScope will be disposed (and possibly committed).
    }
}

The dbContext must be registered as

builder.RegisterType<EFUnitOfWork>().As<IEFUnitOfWork>().InstancePerMatchingLifetimeScope("UnitOfWork");

Quite possibly you don't need the IEFUnitOfWork, but you can simply share the DbContext, registering it in the UnitOfWork scope. In other words, the tagged scope of Autofac could replace your class entirely, AFAICT.

Reference to the Autofac documentation:
https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-matching-lifetime-scope

Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53