user9564974
user9564974

Reputation: 11

How to Resolve multiple DBContext call using generic UnitOfWork<TContext> in Autofac

Hi I have created my UnitOfWork as generic and at runtime it should create new instance of DB context with DBContextOption Builder on the basis of TContext passing I have registered Mention DB Context in autofac but how to resolve this at DB Context Constructor Level

DB Context 1 Implemetation

public class DBContext1 : DbContext
{
    public DBContext1(DbContextOptions<DBContext1> options1) : base(options1)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

DB Context 2 Implemetation

public class DBContext2 : DbContext
{
    public DBContext2(DbContextOptions<DBContext2> options2) : base(options2)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

IUnitOfWork interface Implemetation

public interface IUnitOfWork<TContext> where TContext : DbContext, IDisposable
{

}

UnitOfWork class Implemetation

public class UnitOfWork<TContext> : IDisposable, IUnitOfWork<TContext> where TContext : DbContext, new()
{
    private DbContext _context;
    public UnitOfWork()
    {
        _context = new TContext();
    }
}

StartUp Class Implemetation

public class Startup
{
    protected IConfiguration _configuration { get; set; }

    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {

        services.AddMvc();

        services.AddEntityFrameworkSqlServer()
            .AddDbContext<DBContext1>(options =>
            options.UseSqlServer(_configuration.GetConnectionString("DBContext1")))
            .AddDbContext<DBContext2>(options =>
            options.UseSqlServer(_configuration.GetConnectionString("DBContext2")));

        /* Autofac DI Configuration with registering DBContext/DataModule/ServiceModule to it */

        var containerBuilder = new ContainerBuilder();

        containerBuilder.RegisterInstance(_configuration).AsImplementedInterfaces().ExternallyOwned();

        var autoFacOptions1 = new DbContextOptionsBuilder<DBContext1>().UseSqlServer(_configuration.GetConnectionString("DBContext1")).Options;
        var autoFacOptions2 = new DbContextOptionsBuilder<DBContext2>().UseSqlServer(_configuration.GetConnectionString("DBContext2")).Options;


        containerBuilder.Register(c => new DBContext1(autoFacOptions1)).As<DbContext>();
        containerBuilder.Register(c => new DBContext2(autoFacOptions2)).As<DbContext>();
        containerBuilder.RegisterModule<DataModule>();
        containerBuilder.RegisterModule<ServiceModule>();

        containerBuilder.Register<String>(c => Guid.NewGuid().ToString())
           .Named<String>("correlationId")
           .InstancePerLifetimeScope();

        containerBuilder.Populate(services);
        var container = containerBuilder.Build();

        return new AutofacServiceProvider(container);

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Account}/{action=Login}/{id?}");
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

I am able to achieve multiple DBContext Call as required but I have to create Default constructor & connection string in DB context like mention below

DB Context 1 Implemetation

public class DBContext1 : DbContext
{
    public DBContext1()
    {

    }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Data Source=Server;Database=DB;User Id=UserID;Password=Password;Integrated Security=False;MultipleActiveResultSets=true;");
    }

    public DBContext1(DbContextOptions<DBContext1> options1) : base(options1)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

DB Context 2 Implemetation

public class DBContext2 : DbContext
{
public DBContext2()
    {

    }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Data Source=Server;Database=DB;User Id=UserID;Password=Password;Integrated Security=False;MultipleActiveResultSets=true;");
    }

    public DBContext2(DbContextOptions<DBContext2> options2) : base(options2)
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }
}

Please help me to call parameterised constructor of DBContext1 & DBContext2 using autofac dependency resolver

Upvotes: 1

Views: 610

Answers (1)

Alexander Leonov
Alexander Leonov

Reputation: 4794

Well, if you're using autofac to resolve dependencies then why are you trying to do its job for it? :) That's the main problem with your code.

First of all, you don't need to register IConfiguration explicitly. It is already registered in the IServiceCollection that's passed to ConfigureServices() method and will be automatically picked up by autofac during containerBuilder.Populate(services) call. You can just remove this registration and nothing will change.

Further, you're registering both your DbContexts twice - in the service collection and in the autofac container builder. This is not necessary as the latter will effectively replace the former. Also, it creates confusion about what is registered where and how this whole this is going to work. It's better to pick one method of registration and stick with it.

Next problem: how are you going to unit test your unit of work? It has hard dependency on DbContext whose lifecycle you cannot control in tests. This is exactly what you need autofac for: manage component's dependencies for you allowing you to concentrate on the component's purpose and not on the secondary stuff.

Next confusion point is here:

    containerBuilder.Register(c => new DBContext1(autoFacOptions1)).As<DbContext>();
    containerBuilder.Register(c => new DBContext2(autoFacOptions2)).As<DbContext>();

By doing this you are effectively replacing first db context registration with the second. From this point there is no way to inject DBContext1 anywhere in your application. EDITED: You still can inject collection of DbContext derivative implementations and find DBContext1 among them... but that would look very weird.

All in all, this can be done in much more clean and straightforward way.

Startup

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        var builder = new ContainerBuilder();

        builder.Register(c => c.CreateDbContextOptionsFor<DBContext1>("DBContext1")).As<DbContextOptions<DBContext1>>().SingleInstance();
        builder.Register(c => c.CreateDbContextOptionsFor<DBContext2>("DBContext2")).As<DbContextOptions<DBContext2>>().SingleInstance();

        builder.RegisterType<DBContext1>().AsSelf().InstancePerLifetimeScope();
        builder.RegisterType<DBContext2>().AsSelf().InstancePerLifetimeScope();

        builder.RegisterType<SomeComponent>().As<ISomeComponent>().InstancePerLifetimeScope();

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

        builder.Populate(services);

        var container = builder.Build();
        return new AutofacServiceProvider(container);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ....
    }
}

CreateDbContextOptionsFor helper implementation. It is introduced in order to make Startup code concise and more readable. It can probably be improved even further by making use of autofac's parameterized factory instead of new DbContextOptionsBuilder<TContext>(), but I'm not sure if there's a point in it in this case.

public static class DBExtentions
{
    public static DbContextOptions<TContext> CreateDbContextOptionsFor<TContext>(this IComponentContext ctx,
        string connectionName) where TContext : DbContext
    {
        var connectionString = ctx.Resolve<IConfiguration>().GetConnectionString(connectionName);
        return new DbContextOptionsBuilder<TContext>().UseSqlServer(connectionString).Options;
    }
}

UnitOfWork

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext
{
    private TContext _context;
    public UnitOfWork(TContext context)
    {
        _context = context;
    }
}

Injecting and using unit of work

public class SomeComponent : ISomeComponent
{
    private readonly IUnitOfWork<DBContext1> _uow;

    public SomeComponent(IUnitOfWork<DBContext1> uow)
    {
        _uow = uow;
    }

    public void DoSomething()
    {
        _uow.DoWhatever();
    }

....

Upvotes: 1

Related Questions