0

I have asked this question in a different way on another thread. I probably was not clear enough explaining what I am trying to do, so I did not get a resolution or guidance to solve my problem. Since I don't have enough privileges to add or modify my original post and code, I have to repeat the question in a slightly different light. So, please don't mark this as a duplicate question.

[Edit]

First off, thank you all for trying to help me solve this issue. In spite of all your help, I still have not found an amicable solution. I am beginning to worry that I may not be as intelligent and smart as I thought. Otherwise, this should not be this hard of a problem to solve. I am probably not asking the right questions. So, I am going to present my question in yet another way.

Here are my interfaces:

public interface IDataContext : IDisposable
{
    int SaveChanges();
}

public interface IDataContextAsync : IDataContext
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
    Task<int> SaveChangesAsync();
}


public class DataContext : DbContext, IDataContextAsync
{
    bool _disposed;

    public DataContext(string nameOrConnectionString) : base(nameOrConnectionString)
    {
    }
}

public interface IUnitOfWork : IDisposable
{
    IDataContext DataContext { get; }
    int SaveChanges();
    void Dispose( bool disposing );
    IRepository<TEntity> Repository<TEntity>() where TEntity : class, IObjectState;
    void BeginTransaction( IsolationLevel isolationLevel = IsolationLevel.Unspecified );
    bool Commit();
    void Rollback();
}

public interface IUnitOfWorkAsync : IUnitOfWork
{
    Task<int> SaveChangesAsync();
    Task<int> SaveChangesAsync( CancellationToken cancellationToken );
    IRepositoryAsync<TEntity> RepositoryAsync<TEntity>() where TEntity : class, IObjectState;
}

Now, my dbcontexts:

public partial class MasterDB : DataContext
{
    public MasterDB( string nameOrConnectionString ) : base( nameOrConnectionString )
    {
    }
}

public partial class SubscriberDB : DataContext
{
    public SubsDB(string nameOrConnectionString) : base(nameOrConnectionString)
    {
    }
}

Here is my unit of work:

public class UnitOfWork : IUnitOfWorkAsync
{
    private IDataContextAsync dataContext;
    private bool disposed;
    private Dictionary<string, dynamic> repositories;

    public UnitOfWork( IDataContextAsync dataContext )
    {
        this.dataContext = dataContext;
        repositories = new Dictionary<string, dynamic>();
    }
    public int SaveChanges()
    {
        return dataContext.SaveChanges();
    }
    // rest of the code omitted for brevity!....
}

and this is my unity bootstrapper initializer that is called at app start. I have removed other registrations for brevity.

public class PCUnityBootstrapper : UnityBootstrapper
{
    public IUnityContainer RegisterAllDependencies( IUnityContainer container )
    {
        container.RegisterType<IUnitOfWorkAsync, UnitOfWork>( nameof( DBNames.MasterUOW ) );
        container.RegisterType<IUnitOfWorkAsync, UnitOfWork>( nameof( DBNames.SubsUOW ) );
        container.RegisterType<IConnectionStringProvider, ConnectionStringProvider>();
        container.RegisterType( typeof( IRepositoryAsync<> ), typeof( Repository<> ) );
        container.RegisterType<IDataContextAsync, MasterDB>( nameof( DBNames.MasterDB ), new ContainerControlledLifetimeManager(), new InjectionConstructor( "name=" + nameof( DBNames.MasterDB ) ) );
        container.RegisterType<IDataContextAsync, SubsDB>( nameof( DBNames.SubsDB ), new ContainerControlledLifetimeManager(), new InjectionConstructor( "name=" + nameof( DBNames.SubsDB ) ) );
        return container;
    }

    public IDataContextAsync RegisterDBatRunTime( IUnityContainer container, string namedRegistration,
                    string dataSource, string dbName, string userId = null, string password = null )
    {
        var connString = ( new ConnectionStringProvider() ).GetConnectionString( dataSource, dbName, userId, password );
        container.RegisterType<DataContext>( namedRegistration,
                  new InjectionConstructor( "nameOrConnectionString=" + connString ) );
        return container.Resolve<IDataContextAsync>( namedRegistration );
    }
}
  • I have a MasterDB DbContext that derives from DataContext and is for application wide configurations and also for login validations and is a static context.
  • I have a SubsDB DbContext that also derives from DataContext but with a different schema. Due to business requirements, there are multiple sql databases with the SubsDB schema; each one specific to a group that the logged in user belongs to.

Now, the MasterDB can be registered and resolved without any problem since it will never change. The problem is registering and resolving the SubsDB DataContext.

The underlying Database connection is not known until the user has logged in. So, I cannot register this type at application start up call in my UnityBootstrapper even using InjectionFactory or any other factories (to my knowledge at least) because the database name in the connection string is not known).

In order to satisfy "as close to composition root as possible" rule, I have created another method RegisterDBatRunTime method in my bootstrapper. I can't say I like the way I coded the method. But when the user has finished logging in, I can call this method to register the SubsDB with the correct database. The problem here is, I have to keep a reference to my container after it was initialized, so as to register SubsDB type.

So, here are my questions (the edited ones).

  1. I have two named registrations for my UnitOfWork so that I can inject the correct DataContext into it. But how do I inject them into the constructor of UnitOfWork? Even if I use the attribute like below, wouldn't that create a tight coupling between my UnitOfWork and the Unity Container?

    public UnitOfWork([Dependency(nameof( DBNames.SubsUOW ))] IDataContextAsync dataContext ) { this.dataContext = dataContext; repositories = new Dictionary(); }

  2. If I take this route, I can definitely check who the currently logged in user is and then change the database of the dataContext. But will this work?

  3. Even if it does work, wouldn't I need an exact replica of another
    UnitOfWork to take my DBNames.MasterDB to deal with my other schema? That goes against the DRY priciple.

I have been struggling to find a solution for the last full week but not getting anywhere. I have to say that quite a few members here have suggested quite a few solutions. Most of them seem to be viable answers. But when I begin to implement them, I find myself against new brick walls.

I am relatively new to Dependency Injection and IoC containers but have quite a few years as a developer under my belt. This is why I am beginning to worry that I am not being able to find an amicable solution to this problem. The deeper I go, the more confused I get. StackOverflow is one of the very few places where I find some code samples using Unity IoC. Other places, I can only find boilerplate code that does not seem to educate me in my unique situation. Or is my problem all that unique after all?

All I want is: implement DI the right way and avoid tight coupling between my classes and components. Resolve some of my instances using runtime information without abusing the IoC container as a service locator.

I am going crazy for the past few days on this one. So, someone please help. I am a visual person. So, some code implementing my unique scenario would greatly alleviate my pain. Thanks.

Community
  • 1
  • 1
Babu Mannavalappil
  • 447
  • 2
  • 9
  • 27
  • You shouldn't inject runtime data into your application components: https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99 – Steven Jul 12 '16 at 20:33
  • Is there any specific reason you need a different connection string per user? Since all of the database schema are exactly the same, why not add a `UserId` field to the appropriate tables and `where Table.UserId = ` to the appropriate queries? Then all users can use a single *application-level* connection string. You could even dump the extra user database and add the tables into a *single* database. What are you going to do when you need to add a bunch of fields to some of the tables? How will you handle that deployment nightmare? – NightOwl888 Jul 12 '16 at 21:41
  • @NightOwl: I totally get your concern. I have the same concerns myself about having a database per user. But that is a business requirement. The business has its reasons. Anyway, my cry for help still remains. IMHO, it is not a serious violation of any design pattern to inject the connect string at runtime. I don't think I am injecting **data** at runtime. Just the connection parameter to get to the data. – Babu Mannavalappil Jul 13 '16 at 10:27
  • Okay, since you cannot go the simple route, I would say your main issue is that you are trying to put an *application design* issue into your DI container. DI is for composing the application graph. Runtime data such as connection strings and DbContexts based on those strings should be managed by your application. You should create an abstract factory for each case (that can be injected). You also need to build the lifetime management of DbContext into your app either by reference counting or using ambient context (IDbContextAccessor). Read Mark Seemann's book - it provides the info. – NightOwl888 Jul 13 '16 at 13:41

1 Answers1

0

I have edited my answer to elaborate. Not sure if it is the right solution, but here goes :)

InjectionFactory

// singleton class, but let Unity handle that by using ContainerControlledLifetimeManager
public class DatabaseNameProvider
{
    public string Name { get; set; }
}

public class LoginViewModel
{
    private DatabaseNameProvider databaseNameProvider;
    public LoginViewModel(DatabaseNameProvider databaseNameProvider)
    {
        this.databaseNameProvider = databaseNameProvider;
    }

    private void Login(string username, string password)
    {
         // do login logic
         databaseNameProvider.Name = // insert database name resolution logic
    }
}

public static IUnityContainer InitializeContainer(IUnityContainer container )
{
    container.RegisterType<DatabaseNameProdivder>(new ContainerControllerLifetimeManager()); // singleton
    container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
    container.RegisterType<IDataContextAsync, DataContext>( 
                 new InjectionFactory(container => 
                 {
                     var databaseName = c.Resolve<DatabaseNameProvider>().Name;
                     return new DataContext(databaseName);
                 }));
    container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
    return container;
}

You login ViewModel would be dependent on DatabaseNameProdiver and apply the name after login.

This of course only works if your DataContext is created after the login is complete.


Factory pattern

public class DbContextFactory
{
    private DatabaseNameProvider  provider;
    public DbContextFactory(DatabaseNameProvider provider)
    {
         this.provider = provider;
    }

    public DbContext Create()
    {
         return new DbContext(provider.Name);
    }
}

public class MasterDb : DataContext
{
    public MasterDb(DatabaseNameProvider provider) : base(provider.Name)
    {

    }
}

public static IUnityContainer InitializeContainer(IUnityContainer container )
{
      container.RegisterType<DatabaseNameProdivder>(new ContainerControllerLifetimeManager()); // singleton
      container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
      container.RegisterType<IDbDataContextFactory, DbDataContextFactory>();
      container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
      return container;
 }

Think the best approach for you are the Factory pattern. That way the data context will be constructed when you need it and you know which database you need to access.

Another solution is to make if-else statements when accessing data.

Another note, having logic being dependent on or configured in IoC is a bad pattern.

Michael
  • 3,350
  • 2
  • 21
  • 35
  • Thanks Michael. Your solution looks good to me. But I am still bit blurry putting them all together. Is "DatabaseNameProvider" a factory method or Abstract Factory? Can you include just a few lines of code about DatabaseNameProvider implementation. Also, can you elaborate a little bit (with sample code if possible) about your last edits please? I am finding it really hard to wrap my head around type resolution and the implementation of a factory with an IoC container. Thanks. – Babu Mannavalappil Jul 15 '16 at 18:23
  • Thanks Michael! I think I get your first solution. That should work as long as I call the unity registration after the login as you said. What would the Unity registration and resolution look like in the case of your DbContextFactory? Is is possible at all to register everything upfront and then just change the connection string at some point and resolve the DbContext without actually violating (using container as a service locator) DI pattern? – Babu Mannavalappil Jul 17 '16 at 10:14
  • I have marked your answer as accepted. It took me a while. I came up with implementing a solution by combining suggestions and ideas from multiple members. Thanks. – Babu Mannavalappil Jul 31 '16 at 13:45