1

My Asp.Net MVC application is setup as follows. There are 4 projects in solution.

  • ge.Web
  • ge.BLL
  • ge.Core
  • ge.Entities

Controller in ge.Web initializes a repository object present in ge.Core

 public class MapsController : Controller
 {
      private AssessmentRepository repAssessments = new AssessmentRepository("name=GEContext", schoolCode);

      public ActionResult DisplaySearchResults()
      {
        .....
      }
  }

Assessments Repository

  public class AssessmentRepository : Repository<Assessment>, IAssessmentRepository
{
    public AssessmentRepository(string connString, string schoolCode)
        :base(connString, schoolCode)
    { }
 }

Repository

  public class Repository<TEntity> : IRepository<TEntity> where TEntity:class
{
    protected readonly GEContext context;


    public Repository(string connString, string schoolCode) {
        context = new GEContext(connString);
    }
 }

GEContext

 public class GEContext : DbContext
 {
    public GEContext(string connString):base(connString) 
    {
        this.Configuration.LazyLoadingEnabled = false;
        Database.SetInitializer(new MySqlInitializer());
    }
  }

DbContext

public class DbContext : IDisposable, IObjectContextAdapter
{
    public DbContext(string nameOrConnectionString);
}

Web.Config

 <add name="GEContext" connectionString="server=localhost;port=4040;uid=root;pwd=xxx;database=ge" providerName="MySql.Data.MySqlClient" />

now i want to replace "database=ge" present in web.config with database=ge_[schoolCode]. at runtime How can i go about it?

UPDATE My solution did not work. so i am stating the problem once again.

Web.Config

I have changed My config file to the following (previously GEContext was the only connection string)

<connectionStrings>
<add name="GEContext_sc001" connectionString="server=localhost;port=4040;uid=root;pwd=blabla;database=db_sc001" providerName="MySql.Data.MySqlClient" />
<add name="GEContext_sc002" connectionString="server=localhost;port=4040;uid=root;pwd=blabla;database=db" providerName="MySql.Data.MySqlClient" />

<appSettings>
     <add key="SchoolCodes" value="sc001,sc002"/>

these are the allowed schoolCodes

Now when the user enters schoolcode at login screen, it is validated against the codes present in SchoolCodes key. and if yes, then it should try to connect to the connectionString for that particular connection. Now when my code comes to

UserManager.FindAsync

in Login function of AccountController, it crashes trying to find GEContext. Where is that set? and how can i change it?

I have changed the repository calling in controller as follows

private static string schoolCode = (string)System.Web.HttpContext.Current.Session["SchoolCode"];

    private AssessmentRepository repAssessments = new AssessmentRepository("name=GEContext_" + schoolCode);

UPDATE-2 Following is present in ge.Web

IdentityConfig.cs

  public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
    public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
        : base(store)
    {
    }

     public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
    {
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>(context.Get<ApplicationDbContext>()));
  ...........
 }

The following is present in ge.Core

ApplicationDbContext

 public class ApplicationDbContext : IdentityDbContext<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>
{

     public ApplicationDbContext(string connString)
        : base(connString)
    {
        Database.SetInitializer(new MySqlInitializer());
    }

    public static ApplicationDbContext Create()
    {

        return new ApplicationDbContext("name=GEContext_");
    }
}

How can i pass schoolCode from ge.web to ge.Core (answer should be straight forward but currently i cant get my head around it)

UPDATE-3

As told by itikhomi and taking help from this post I have changed my code as follows

  1. in ApplicationDbContext class added the following

    public static ApplicationDbContext Create(string scCode){ return new ApplicationDbContext("name=GEContext_" + scCode); }

  2. in AccountController Login

    var appDbContext = ApplicationDbContext.Create(model.SchoolCode);

                Request.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
    

it still does not hit the correct database

Samra
  • 1,815
  • 4
  • 35
  • 71
  • You just alter the web.config. You can also add multiple connection string values "GEContext", "GEContext2", "GEContext3" then have your program select one of these connections dynamically. – Jasen Jul 26 '17 at 01:18
  • my database name would be db_[schoolCode]..so i basically need to validate the schoolcode entered at login.. i try to connect with the entered schoolCode by renaming database name in connection string – Samra Jul 26 '17 at 01:28
  • What you're asking is quite broad. Here's some guidance https://stackoverflow.com/questions/35956345/entity-framework-multi-tenant-architecture-with-multiple-databases-generics and http://benfoster.io/blog/aspnet-core-multi-tenancy-data-isolation-with-entity-framework – Jasen Jul 26 '17 at 02:33

3 Answers3

2

You can change the database for an open connection

context.Database.GetDbConnection().ChangeDatabase("");
Lakmal
  • 779
  • 1
  • 8
  • 16
1

You have two ways

1)

    using System.Data.SqlClient;

         public class Repository<TEntity> : IRepository<TEntity> where TEntity:class
        {
            protected readonly GEContext context;


            public Repository(string connString, string schoolCode) {
                context = new GEContext(connString);
                var connection = new SqlConnectionStringBuilder(context.Database.Connection.ConnectionString);
                connection.InitialCatalog = "YOUR_PREFIX_FROMSOMEWHERE"+schoolCode;
                context.Database.Connection.ConnectionString = connection.ConnectionString;
            }
         }

2) if you wants to switch connection when it opened before use ChangeDatabase:

//open connection if it close
    context.Database.Connection.ChangeDatabase("DATABASE-NAME");

NOTE: if use ChangeDatabase connection should be already opened

FOR UPDATE3:

You need to do somethink like this:

 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {

        }

        public ApplicationDbContext(string schoolCode)
            : base(schoolCode)
        {
            var connection = new SqlConnectionStringBuilder(this.Database.Connection.ConnectionString);
            connection.InitialCatalog = "YOUR_PREFIX_FROMSOMEWHERE" + schoolCode;
            this.Database.Connection.ConnectionString = connection.ConnectionString;
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }

in account controller:

public ApplicationSignInManager SignInManager
        {
            get
            {

                if (_signInManager == null)
                {
                    var code = HttpContext.Request.Form.Get("SchoolCode");//Get from FORM\QueryString\Session whatever you wants
                    if (code != null)
                    {
                        HttpContext.GetOwinContext().Set<ApplicationSignInManager>(new ApplicationSignInManager(_userManager, HttpContext.GetOwinContext().Authentication));
                    }
                    _signInManager = HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
                }

                return _signInManager;
            }
            private set
            {
                _signInManager = value;
            }
        }

 public ApplicationUserManager UserManager
        {
            get
            {
                if (_userManager == null)
                {
                    var code = HttpContext.Request.Form.Get("SchoolCode");//Get from FORM\QueryString\Session whatever you wants
                    if (code != null)
                    {
                        var appDbContext = new ApplicationDbContext(code);

                        HttpContext.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
                        HttpContext.GetOwinContext().Set<ApplicationUserManager>(new ApplicationUserManager(new UserStore<ApplicationUser>(appDbContext))); //OR USE your specified create Method
                    }
                    _userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
                }
                return _userManager;
            }
            private set
            {
                _userManager = value;
            }
        }

Your problem is in Store of UserManager is created before you change your OWIN context, in this case better to use DI like here

itikhomi
  • 1,560
  • 13
  • 13
  • Thanks.. i have edited my application a bit can you please check the updates above? – Samra Jul 26 '17 at 06:32
  • @Samra, try to debug and provide me schoolCode value in AccountController, i think it will be null and it's why you get the error – itikhomi Jul 26 '17 at 06:37
  • the problem is not with school code it is present but when it goes to Login function in accountController but crashes at var user = await UserManager.FindAsync(model.Username, model.Password); – Samra Jul 26 '17 at 06:42
  • It is trying to find GEContext here – Samra Jul 26 '17 at 06:42
  • @Samra, please provide Identity configuration, there are manulaly set your old GEContext, or just find it by searching "GEContext" – itikhomi Jul 26 '17 at 06:53
  • identity configuration means? – Samra Jul 26 '17 at 06:56
  • yes it was not changed in public class ApplicationDbContext : IdentityDbContext..I think this was the issue lemme check – Samra Jul 26 '17 at 06:58
  • @Samra, it's right, your ApplicationDbContext contains old :base("GEContext") – itikhomi Jul 26 '17 at 07:10
  • Hi can you please check update-2 now i have a different question – Samra Jul 26 '17 at 07:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/150148/discussion-between-itikhomi-and-samra). – itikhomi Jul 26 '17 at 07:22
  • @Samra, how you want get schoolCode if you dont know what user is it?=) – itikhomi Jul 26 '17 at 07:27
  • @Samra, About Update2, add another constructor ApplicationDbContext(scCode) and set context with scCode,then use new UserManager in AccountController var usermanager = new ApplicationUserManager(new UserStore(new ApplicationDbContext(scCode))); usermanager.FindAsync(login,password); – itikhomi Jul 26 '17 at 08:10
  • when Create method in IdentityConfig is hit, new UserManager in AccountController does not have the school code info yet as the login page is not yet loaded – Samra Jul 27 '17 at 01:59
0

I resolved it with the help of itikhomi..Posting the final code..

ApplicationDbContext

public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext("name=GEContext");
    }

AccountController

 public ApplicationUserManager UserManager {
        get
        {
            if (System.Web.HttpContext.Current.Session["SchoolCode"] == null)
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            else
            {
                var appDbContext = ApplicationDbContext.Create(System.Web.HttpContext.Current.Session["SchoolCode"].ToString());//new ApplicationDbContext("name=GEContext", System.Web.HttpContext.Current.Session["SchoolCode"].ToString());

                HttpContext.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
                HttpContext.GetOwinContext().Set<ApplicationUserManager>(new ApplicationUserManager(new UserStore<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>(appDbContext)));

                return HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
        }
        private set
        {
            _userManager = value;
        }
    }
Samra
  • 1,815
  • 4
  • 35
  • 71