1

I am trying to create DBContect object by passing connection string at run time. Following is the structure of my NiNject Repository implementation.

public class HomeController : ApiController
{
    MyService _service{ get; set; }

    public HomeController(MyService service)
    {
        _service= service;
    }
}

public class MyService 
{
    IRepository _repo { get; set; }

    public MyService(IRepository repo)
    {
        _repo = repo;
    }
}

Repository implementation is as follows :

public interface IRepository
{
    TenantDbContext _db { get; set; }
    void Add<T>(T entity) where T : class;
    void Delete<T>(int id) where T : class;
    T Find<T>(int id) where T : class;
    IQueryable<T> Query<T>() where T : class;
    void SaveChanges();

    MasterDbContext _db_master { get; set; }
    void Add_Master<T>(T entity) where T : class;
    void Delete_Master<T>(int id) where T : class;
    T Find_Master<T>(int id) where T : class;
    IQueryable<T> Query_Master<T>() where T : class;
    void SaveChanges_Master();
}

public class Repository : IRepository
{
    public TenantDbContext _db { get; set; }
    public MasterDbContext _db_master { get; set; }

    public Repository(TenantDbContext db)
    {
        _db = db;
    }
    public Repository(MasterDbContext db_master)
    {
        _db_master = db_master;
    }
    public IQueryable<T> Query<T>() where T : class
    {
        return _db.Set<T>().AsQueryable();
    }
    public IQueryable<T> Query_Master<T>() where T : class
    {
        return _db_master.Set<T>().AsQueryable();
    }
//.....Rest of the implemetation
}

Here goes my TenantDBContext class which takes an argument as a database string. No default constructor

 public class TenantDbContext : DbContext
{
    public TenantDbContext(string connString)
        : base(connString)
    {
        //Configuration.AutoDetectChangesEnabled = true;
        //Configuration.LazyLoadingEnabled = false;
        //Configuration.ProxyCreationEnabled = false; //change tracking 
    }

    public static TenantDbContext Create(string DbString)
    {
        // Some logic to get the tenant database string. 
        // Presently i am just passing it hard coded as follows.

        return new TenantDbContext(DbString);
    }
}
public class MasterDbContext : IdentityDbContext<ApplicationUser>
{
    public MasterDbContext() : base("MasterDBConnection", throwIfV1Schema: false)
    {
       // dbmigration.AutomaticMigrationsEnabled = true;
        Configuration.ProxyCreationEnabled = false;
        Configuration.LazyLoadingEnabled = false;
    }
    public static MasterDbContext Create()
    {
        return new MasterDbContext();
    }

    //public DbSet<ApplicationUser> ApplicationUsers { get; set; }
    public DbSet<Tenant> Tenants { get; set; }
    public DbSet<TenantUserMap> TenantUserMaps { get; set; } }

Finally, RegisterServices which i have in my NinjectWebCommons.cs looks as follows : Each Tenant have its different database. We are fetching out the Tenant name from the access token on every request and caching that requested Tenant object so we can pass the correct Tenant Database string in order to do the operations on the requested Tenant Database.

Below snippet, We are fetching the Tenant object from the current request cache which will provide us the Tenant Database string of the requested client.

 public Tenant Tenant
    {
        get
        {
            object multiTenant;
            if (!HttpContext.Current.GetOwinContext().Environment.TryGetValue("MultiTenant", out multiTenant))
            {
                throw new ApplicationException("Could Not Find Tenant");
            }
            return (Tenant)multiTenant;
        }
    }

private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IRepository>().To<Repository>();
        kernel.Bind<TenantDbContext>().ToMethod(_ => 
        TenantDbContext.Create(Tenant.DBString)); 
        kernel.Bind<MasterDbContext>().ToMethod(__ => MasterDbContext.Create());
    }   

Problem : When i add second binding in my NinjectWebCommons.cs "kernel.Bind()" , i start getting exception saying that "No default constructor found". It is simply not taking two bindings with the kernal. Request you to please have a look at the code above and point me out where i am going wrong.

I will appreciate your help. Thanks in Advance.

Tarun
  • 23
  • 5
  • you need to tell your container how to build up your `DbContext`. I don't use Ninject, but the logic is like `kernel.[when looking for TenantDbContext].[Use this constructor with this connString]` – Jonesopolis Oct 16 '17 at 12:28
  • look at [this answer](https://stackoverflow.com/questions/35308511/how-to-make-ninject-choose-a-specific-constructor-without-using-injectattribute) – Jonesopolis Oct 16 '17 at 12:58
  • Thanks Jonesopolis for your prompt reply. I tried to do as you suggested as follows :
    `kernel.Bind().To().WithConstructorArgument("connString", "Data Source = something; Initial Catalog = something; uid = something; pwd = something");`
    I know its wrong, just wanted to check if it even runs at all or not. Surprisingly it started executing but the connection string doesn't forms.
    – Tarun Oct 16 '17 at 13:03

1 Answers1

0

You may add a binding for the database context and point Ninject to use your factory method:

kernel.Bind<TenantDbContext>().ToMethod(_ => TenantDbContext.Create());
Peit
  • 882
  • 6
  • 17
  • Thanks a ton Peit, it just worked like a boss. You guys have such clear concept. :) – Tarun Oct 16 '17 at 13:32
  • Hi Peit, I am trying to add another ToMethod but then NInject stops working by giving no default constructor error. `kernel.Bind().ToMethod(_ => TenantDbContext.Create());` `kernel.Bind().ToMethod(__ => MasterDbContext.Create());` – Tarun Dec 26 '17 at 16:28
  • It works with only one "ToMethod" at a time. How can i use multiple "ToMethod" ? Thanks! – Tarun Dec 26 '17 at 16:38
  • You can use as many `.ToMethod(...)` bindings as you like. The code you shared looks good to me so I bet the problem is not caused by multiple bindings. You need to share some more details, like the exception. – Peit Jan 08 '18 at 10:16
  • Thanks Peit, i will share you the exact exception i am getting. I have landed into another problem with this. Request you to please have a look at the original post and please let me know if you have solution to this. Thanks! – Tarun Jan 08 '18 at 10:55
  • Well, there is no need for the Tenant property to be non-static (although I am not arguing for statics). From the property you are accessing the http-context by calling `HttpContext.Current` which is a static property so you might simply make this property static and use it in the `.ToMethod` call. Unless you configure Ninject to serve a singleton or maintain the lifetime on its own in the binding, the `_ => TenantDbContext.Create(...)` lambda will be called everytime so the Tenant will also be resolved every time from the property. – Peit Jan 08 '18 at 11:06
  • Anyway, I personally would avoid such constructs because you may run into other difficulties with that. Imagine you try to resolve an instance which is not related to an HTTP request. Ninject would fail then and you'd probably not know why that is. E.g. if you use `async/await` you _may_ end up on a different thread which _may_ lose relation to the original request which _may_ lead to unexpected behavior. – Peit Jan 08 '18 at 11:08
  • Thanks Peit for your valuable suggestions. If you think such constructs would fall into multi-threading issues then what alternate way do you suggest. Given the fact that the DAL layer is a separate class library & we would not get HttpContext.Current.GetOwinContext().Environment there. – Tarun Jan 08 '18 at 11:17
  • That is very hard to say because it depends on your entire application. I am not saying that the approach is bad at all. It simply has its disadvantages. Sometimes there is no better way. I'd definitely not put the owin-context into the DAL library. That's exactly what DI is used for. The repository should not have to care about that. I'd suggest you to keep it as is (as long as it is running) but keep in mind that it might lead to problems like if you need access to the database outside of a request and so on. I'd appy refactoring then if necessary. – Peit Jan 08 '18 at 11:21
  • Thanks Peit. Appreciate your valuable inputs on the same. – Tarun Jan 09 '18 at 06:32
  • Peit, i have updated my code in the original post. The problem of 2 bindings with kernal is still coming. Please have a look at my updated code and guide me where i am going wrong. The exception its throwing is "..does not have a default constructor" which means NInject is not working. Thanks! – Tarun Jan 10 '18 at 13:33
  • Sorry for bothering you, I have figured out the problem in my code. Actually i was having over loaded Repository constructors for Tenant & MasterTenant database context. I have passed both of them in just one Repository constructor & it worked like a charm :D `public Repository(TenantDbContext db, MasterDbContext db_master) { _db = db; _db_master = db_master; }` – Tarun Jan 10 '18 at 13:46