1

I am trying to refactor an existing solution (didn't write it) to use DI and Autofac, and have run into a bit of a problem. The solution in question supports a number of SQL database types (MSSQL, MySQL, PostgreSQL, potentially more.), and the user can have a number of databases of various kinds connected in the app (ie. they may have both an MSSQL and PostgreSQL DB connected). When a DB is connected to the app the type is stored in an enum. When actions are taken on the DBs in question it currently uses the following static factory to return a Datalayer:

public static class DatabaseFactoryController
{
    public static IDatalayer GetDatalayer(ConnType databaseType)
    {
        switch (databaseType)
        {
            case ConnType.Mssql:
                return new MssqlModel());
            case ConnType.Postgre:
                return new PostgreModel());
            case ConnType.MySql:
                return new MysqlModel());
            default:
                throw new ArgumentOutOfRangeException(nameof(databaseType));
        }
    }
}

Now the problem is that other controllers in the code need to have the proper datalayer injected for the DB the user is currently accessing like eg:

public class SizeController : IDataController
{
    private readonly IDatalayer _datalayer;

    public SizeController(IDatalayer datalayer)
    {
        _datalayer = datalayer;
    }

    public Response<SizeInfo> GetData(IRequest request)
    {
        <actions taken here using datalayer>
    }
}

But how can I wire up Autofac to dynamically inject the right datalayer in the controllers, for the DB chosen for any given call (instantiation of the controller) without going to a ServiceLocator. I'm pretty sure it has to be possible but I'm pretty new to Autofac so maybe that's why I can't seem to make it work based on the documentation. I've tried following this: https://benedict-chan.github.io/blog/2014/08/13/resolving-implementations-at-runtime-in-autofac/

builder.RegisterType<MssqlDatalayer>()
            .As<IDatalayer>().Keyed<IDatalayer>(ConnType.Mssql);

        builder.RegisterType<PostgreDatalayer>()
            .As<IDatalayer>().Keyed<IDatalayer>(ConnType.Postgre);

        builder.Register<Func<ConnType, IDatalayer>>(c =>
        {
            var componentContext = c.Resolve<IComponentContext>();
            return (roleName) =>
            {
                var dataService = componentContext.ResolveKeyed<IDatalayer>(roleName);
                return dataService;
            };
        });
        builder.RegisterType<SizeController>().As<IDataController>()

but in that implementation I fail to see how I pick the implementation to inject into the constructor at runtime. Maybe I'm missing something massively obvious, or maybe I need to fundamentally refactor the code. Any input will be valued as I've been stuck on this problem for a while now.

  • `they may have both an MSSQL and PostgreSQL DB connected` In that case, which IDatalayer should it choose? The MS one or the Postgres one? – mjwills Jun 22 '17 at 12:34
  • The one with the ConnType of the request being made, but I think I see now given Aleksey's answer below. I'll have to pass in the ConnType as a parameter during runtime, and then it can select the right implementation. – Kasper Bergh Jun 23 '17 at 05:54

1 Answers1

1

As it shown in the article you used

public class SizeController : IDataController
{
    private readonly IDatalayer _datalayer;

    public SizeController(Func<ConnType, IDatalayer> dataLayerFactory)
    {
        _datalayer = dataLayerFactory(ConnType.Mssql);
    }

    public Response<SizeInfo> GetData(IRequest request)
    {
        //<actions taken here using datalayer>
        _datalayer.SomeIDatalayerMethod();
    }
}

Or you can use another one approach

makison
  • 373
  • 2
  • 10
  • Thank you. I ended up doing something very similar to this, except storing the Func and taking the ConnType as a parameter for the GetData method to ensure I got the right datalayer with each call. – Kasper Bergh Jun 23 '17 at 11:02