Unable to resolve AutoFac Keyed service with KeyFi

2019-08-02 19:01发布

问题:

I have a generic UnitOfWork pattern implementation and these UnitOfWork objects are dependencies to my service classes. Below snippets should help the reader understand my code setup:

IUnitOfWork interface

public interface IUnitOfWork<out TContext> where TContext : IDbContext

UnitOfWork class

public sealed class UnitOfWork<TContext> : IDisposable, IUnitOfWork<IDbContext> where TContext : IDbContext
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(UnitOfWork<TContext>));

        private readonly IDbContext _dbContext;
        private Dictionary<string, IRepository> _repositories;
        private IDbTransaction Transaction { get; set; }

        public UnitOfWork(IDbContext context)
        {
            _dbContext = context;
        }
    }

Container registrations:

builder.RegisterGeneric(typeof(UnitOfWork<>)).As(typeof(IUnitOfWork<>));

builder.RegisterType<ReconciliationDbContext>().As<IDbContext>();
builder.RegisterType<GenevaDataDbContext>().As<IDbContext>();
builder.RegisterType<OpenStaarsDbContext>().As<IDbContext>();

builder.RegisterType<UnitOfWork<ReconciliationDbContext>>().Keyed<IUnitOfWork<IDbContext>>(ContextKey.Recon);
builder.RegisterType<UnitOfWork<OpenStaarsDbContext>>().Keyed<IUnitOfWork<IDbContext>>(ContextKey.OpenStaars);

builder.RegisterType<CommentsService>().As<ICommentsService>().WithAttributeFiltering();

DbContext classes:

public class ReconciliationDbContext : BaseDbContext<ReconciliationDbContext>, IDbContext
    {
        private const string DbSchema = "BoxedPosition";

        public ReconciliationDbContext() : base("Reconciliation")
        {

        }
    }

public class OpenStaarsDbContext : BaseDbContext<OpenStaarsDbContext>, IDbContext
    {
        public OpenStaarsDbContext() : base("OpenStaars")
        {

        }
    }

CommentsService class:

public class CommentsService : ICommentsService
    {
        private readonly IUnitOfWork<IDbContext> _reconciliationUoW;

        public CommentsService([KeyFilter(ContextKey.Recon)] IUnitOfWork<IDbContext> reconciliationUoW)
        {
            _reconciliationUoW = reconciliationUoW;
        }
    }

Resolving ICommentsService:

var commentsService = container.Resolve<ICommentsService>();

Now when I try to resolve the ICommentsService type, it instantiates the UnitOfWork dependency. However, the UnitOfWork._dbContext property evaluates to OpenStaarsDbContext type. This is particularly strange considering our registrations.

It becomes more strange if we re-order our IDbContext registrations by registering the GenevaDataDbContext after OpenStaarsDbContext. Now the _dbContext evaluates to GenevaDataDbContext instance.

How can I fix this to make the reconciliationUoW dependency of CommentsService to have the correct instance of ReconciliationDbContext ?

回答1:

The reason of such behaviour is the fact you inject IDbContext into your UnitOfWork constructor, instead of TContext - the container just ignores the type you provide as a generic parameter in you registration and takes the first IDbContext it finds in the container - which would be the last registered, no matter what key you would use.

To make it work, instead of using keyed registration, you could simply inject IUnitOfWork<ContextYouNeed> instead of IUnitOfWork<IDbContext> - it would simplify the code as well. First you need to fix your UnitOfWork class:

class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : IDbContext
{
    private readonly TContext _context;

    public UnitOfWork(TContext context)
    {
        _context = context;
    }
}

In your registration, you do not need to register specific unit of work types, standard generic registration would be sufficient. But you need to register your context types AsSelf as well, so Autofac would properly inject it for your unit of work instances:

builder.RegisterGeneric(typeof(UnitOfWork<>)).As(typeof(IUnitOfWork<>));

builder.RegisterType<ReconciliationContext>().As<IContext>().AsSelf();
builder.RegisterType<OpenStaarsContext>().As<IContext>().AsSelf();

Later on, in your service simply inject proper unit of work:

public CommentsService(IUnitOfWork<ReconciliationContext> reconciliationUoW)