0

EDIT: I also have a completely different approach where I have just about all of that crap commented out. I am able to call into my method and I configured the connect string to connect to the server, but not to the database. Then I want to connect to the different database depending on some data passed in (like the database name maybe?). Is there a way to call OnConfiguring on the fly so I can configure my connection string to be different each time I call my method?

I know some of you are going to look at this and roll your eyes with my stupidity, but we all had to start somewhere! I have a scenario with one database server but multiple databases all which share the same schema. Currently I am rewriting code for each database and it is a mess, so I am trying to clear it up. I have gotten myself pretty confused here so I am looking for advice from some of you gurus out there. I am a beginner here and trying to do something I find very advanced here so go easy on me. I will keep my examples with just two databases, but really there are 10+ databases that are all the same that I need to switch between often. My goal is to try to get rid of this 1, 2, 3, 4, 9, 10, etc stuff all over the place when I want to save or access a record, and have one thing controlling it all.

I created a base class for my database context

public partial class opkDataBaseContext : DbContext
{
    private DbContextOptions<opkData1Context> options1;
    private DbContextOptions<opkData2Context> options2;

    public opkDataBaseContext()
    {
    }

    public opkDataBaseContext(DbContextOptions<opkDataBaseContext> options)
        : base(options)
    {
    }

    public opkDataBaseContext(DbContextOptions<opkData1Context> options)
    {
        this.options1 = options;
    }

    public opkDataBaseContext(DbContextOptions<opkData2Context> options)
    {
        this.options2 = options;
    }

    public virtual DbSet<OrgUnits> OrgUnits { get; set; }



    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {


...
    }
       OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);


    public void CreateNewOrganization(CreateCompleteOrgViewModel model)
    {
        var nameParameter = new SqlParameter("@TopLevelOrgName", model.Name);
        var codeParameter = new SqlParameter("@Code", model.Code);
        var DbNameParameter = new SqlParameter("@DBName", model.DbCatalog);
        var debugParameter = new SqlParameter("@debug", "0");
        var firstNameParameter = new SqlParameter("@FirstName", model.FirstName);
        var lastNameParameter = new SqlParameter("@LastName", model.LastName);
        var userNameParameter = new SqlParameter("@Username", model.UserName);

        this.Database.ExecuteSqlRaw("CreateRootOrg @TopLevelOrgName, @Code, @DBName, @debug, @FirstName, @LastName, @Username",
            nameParameter, codeParameter, DbNameParameter, debugParameter, firstNameParameter, lastNameParameter, userNameParameter);

    }

Here is my Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        if (_env.IsProduction())
        {
            var opkCoreConnection = Configuration.GetConnectionString("opkCoreDatabase");
            services.AddDbContext<opkCoreContext>(options => options.UseSqlServer(opkCoreConnection));

            var opkData1Connection = Configuration.GetConnectionString("opkData1Database");
            services.AddDbContext<opkData1Context>(options => options.UseSqlServer(opkData1Connection));

            var opkData2Connection = Configuration.GetConnectionString("opkData2Database");
            services.AddDbContext<opkData2Context>(options => options.UseSqlServer(opkData2Connection));


            var opkDataLocalBaseConnection = Configuration.GetConnectionString("opkDataBaseDatabase");
            services.AddDbContext<opkDataBaseContext>(options => options.UseSqlServer(opkDataLocalBaseConnection));

Then I have one of these for each database:

    public partial class opkData1Context : opkDataBaseContext
{
    public opkData1Context()
    {
    }

    public opkData1Context(DbContextOptions<opkData1Context> options)
        : base(options)
    {
    }

My current error is:

"Error while validating the service descriptor 'ServiceType: Models.DataModels.opkDataBaseContext Lifetime: Scoped ImplementationType: Models.DataModels.opkDataBaseContext': Unable to activate type 'Models.DataModels.opkDataBaseContext'. The following constructors are ambiguous:\r\nVoid .ctor(Microsoft.EntityFrameworkCore.DbContextOptions'1[Models.DataModels.opkDataBaseContext])\r\nVoid .ctor(Microsoft.EntityFrameworkCore.DbContextOptions'1[Models.opkData1Context])"} System.Exception {System.InvalidOperationException}

I have been messing with this all day. First, am I even going down the right path or is this just a dumb idea? Second, any idea where I am going wrong? Thank you!

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
gecclesinc
  • 190
  • 1
  • 15
  • The application needs to use multiple databases in parallel? – Chetan Jun 24 '20 at 23:47
  • How the switching between database happens? – Chetan Jun 24 '20 at 23:49
  • Thank you for your reply. Multiple databases are involved, but I am only saving data to one of them at a time based on selections in my view. They have the same schema. I am onto something, though. I figured out to create a base class and I am able to use OnConfiguring to change my database context and that works to hit the correct database. Where I am stuck now is that I do not know how to pass a value into the base context constructor so I can control which database I am changing to in OnConfiguring. – gecclesinc Jun 24 '20 at 23:52

2 Answers2

2

I have a scenario with one database server but multiple databases all which share the same schema

This is very common, even a best-practice, in Software-as-a-Service (SaaS) applications.

While this is not obvious, it turns out to be quite simple. You just need some way to pick the connection string at runtime, probably based on a combination of the config and something in the HttpContext (like the user's identity, the path, the host header, etc). Then you configure the DbContext for DI like this:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();

        services.AddDbContext<MyDbContext>((sp, opt) =>
        {
            var httpContext = sp.GetRequiredService<IHttpContextAccessor>();
            var config = sp.GetRequiredService<IConfiguration>();

            string connectionString = GetConnectionStringFromHttpContext(httpContext, config);

            opt.UseSqlServer(connectionString, o => o.UseRelationalNulls());

        });

        services.AddControllers();
    }

where GetConnectionStringFromHttpContext is a custom method that builds the connection string based on the config and the HttpContext.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • 1
    Thank you. By a stroke of magic and luck I came pretty close to your answer not long after posting. I am still working on it. I scrapped SO much code and SO many files. It wasn't exactly "easy" for me, but I did figure out I could use HttpContext to pass around my database name and then change the connect string in the OnConfiguring method of my base database class. So nice! Lots of learning today. =) – gecclesinc Jun 25 '20 at 00:36
1

You could take a look a feature introduced in EF Core 5, which can change the connection or connection string on an already initialized context

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • I did read about ef5 having this, but I was trying to stick with 3.1 because I am still so new to this whole language. I didn't want to have that extra layer of complexity dealing with having less documentation out there like ef5 currently has. I need all the help I can get! – gecclesinc Jun 25 '20 at 05:31
  • And you've been through https://stackoverflow.com/questions/36816215/dynamically-change-connection-string-in-asp-net-core - don't like the look of any of those? – Caius Jard Jun 25 '20 at 06:29
  • I did look at those and eventually did use the idea of HttpContext (similar to the answer I accepted here from above). I have a solution now that works very well. So now they can add any number of databases and I will be able to connect to them! – gecclesinc Jun 26 '20 at 02:15