How can I use a single controller with different DBContexts? - c#

I have ~70 databases which are all exactly the same structure.
What I am attempting to do is to create a quick Blazor (WASM/hosted) site on .net 6 which can access all of these sites using EF. Blazor is probably unimportant because all of the magic is happening in the 'hosted' portion which is really just a .Net API project.
So far, I have created a CommonDBContext which inherits DBContext and then individual DBContexts for each database which inherit CommonDBContext.
CommonDbContext.cs
public partial class CommonDbContext : DbContext
{
public CommonDbContext(DbContextOptions<CommonDbContext> options) : base(options)
{
}
protected CommonDbContext(DbContextOptions options) : base(options)
{
}
+2600 lines of EF scaffolding code
DB1-Context.cs
public partial class DB1Context : CommonDbContext
{
public DB1Context(DbContextOptions<DB1Context> options)
: base(options)
{
}
}
These various context are then injected in Program.cs
builder.Services.AddDbContext<DB1Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DB1"));
});
builder.Services.AddDbContext<DB2Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DB2"));
});
I am not convinced this is a wonderful solution for 70 databases but it is a start. This approach seems to work perfectly - I can create a controller and whichever DB context I use, it seems to pull from the proper database.
Here's the problem
How can I select which DBContext I want to use in the controller? Building ~70 duplicate controllers seems very wrong but I haven't figured out how to pass in the DBContext from my request to the controller.
DopplegangerController.cs
[Route("api/[controller]")]
[ApiController]
public class DopplegangerController: ControllerBase
{
private DB1Context_context; // <-- RIGHT HERE
public DopplegangerController(DB1Contextcontext)
{
_context = context;
}
// GET: api/<DopplegangerController>
[HttpGet]
public List<Stuffs> Get()
{
return _context.Stuffs.ToList();
}

In you Program.cs when configuring your services, you can give a function that will fetch the connection string depending on your settings. This function will be call each time the DbContext needs to be injected.
builder.Services.AddDbContext<CommonDbContext>(getSQLOptions, ServiceLifetime.Transient);
void getSQLOptions(IServiceProvider serviceProvider, DbContextOptionsBuilder dbContextBuilder)
{
YourSettings? settings = serviceProvider.GetService<YourSettings>();
IHttpContextAccessor? httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
/* Get your connectionString from your settings and HttpContext (Can be headers, query param or whatever) */
dbContextBuilder.UseSqlServer(connectionString);
}
You can then just inject your unique CommonDbContext in your Controller and use it. You don't need the child DbContext anymore

Related

Access DBContext in ASP.NET Core 6 from data access layer class

I have an ASP.NET Core 6 MVC app that I created from a VS 2022 template. I'm writing a custom data access layer and business logic layer.
I know I can pass the _context from the controllers down through the BLL in the DAL, however I would prefer to have direct access from the DAL. I don't see any reason the BLL or the web code need to have anything to do with data access.
I've tried several examples of injection, but I can't seem to get any of them to work. Does anyone have a good solution?
Edit:
In Project.cs I have added DbContext:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
I would like to get access to DbContext in my custom data access layer without having to pass DbContext all the way down from a Controller into my DAL contructor like so:
namespace MyProject.Code
public class CustomDataAccessLayer
{
public string[] GetCustomers()
{
DbConnection conn = DbContext.GetConnection();
//Whatever query logic I want to do here
Is there any way to access DbConext directly without having to pass it in from within a controller action?
Here you can read about how DI works exactly.
I'll provide some example of how you're supposed to do that.
first you need to have DataContext Class that extends DbContext Class that will be like this
using Microsoft.EntityFrameworkCore;
namespace MyProject.Context
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<MyEntity> Entities { get; set; }
}
}
In the Program.cs you need to add theDataContextlike this
builder.Services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(builder.Configuration
.GetConnectionString("DefaultConnection")); // for example if you're holding the connection string in the appsettings.json
});
and then in the Logic class that you want to use it in make a data member in the class of type DataContext
private readonly DataContext _dataContext;
and your constructor must take DataContext as parameter and Initialize your constructor will look like this
public ClassName(DataContext dataContext)
{
_dataContext = dataContext;
}
you don't need to add dbcontext to a controller, nobody does it if there is a data access layer, DI will automatically inject the context
public class CustomDataAccessLayer
{
private readonly ApplicationDbContext _context;
public CustomDataAccessLayer(ApplicationDbContext context)
{
_context = context;
}
public string[] GetCustomers()
{
//your code
}
}
with json :
var constr = builder.Configuration["ConnectionStrings:Default"];
builder.Services.AddDbContext<AppDbContext>(opt => {
opt.UseSqlServer(constr);
});
Json :
"ConnectionStrings": {
"Default": "Server=Victus;Database=TeamDb;Trusted_Connection=true;"
}
AppDbContext.cs :
public class AppDbContext:DbContext {
public AppDbContext(DbContextOptions <AppDbContext> options) : base(options) { }
public DbSet<team> teams { get; set; }
}
Home Controller :
private AppDbContext _appDbContext;
public HomeController(AppDbContext appDbContext) {
_appDbContext = appDbContext;
}
public IActionResult Index() {
return View(_appDbContext.teams);
}

Use different Database, with different DBcontext, and connect to it according to different URL request

we actually have an online app, which include a ChatBot.
This chatbot can show us different informations from a database, like
Me : "What is the estimated budget for Projcet1"
Chat bot : "The estimated budget is 50k € for Project1"
Fine. Now, we will deploy this application for different companies. And each company have a different Database.
For those different Database (2 at the moment) I created 2 context. I have 2 models to interact with those database, and I can add Controller for every models.
services.AddDbContext<data_firstContext>(options =>
{
options.UseMySQL(Configuration.GetConnectionString("Database1"));
});
services.AddDbContext<data_secondContext>(options =>
{
options.UseMySQL(Configuration.GetConnectionString("Database2"));
});
The question now is, how can I chose to connect either to the first database, or to the second database.
I can send from the front-end, the URL header of the App or something like that, or just catch it somewhere I guess, but how can I switch from one to an other depending on the Url request, or something I give in parameter from the front-end.
I looked online but none of what I've tried actually work. If someone have an idea, it'd really help me !
Thanks
Assuming you are using EF core, you don't have to pass any parameter as such from the UI. You can do that directly by defining different DBContexts and use them in the constructors of your services by using Dependency Injection. For eg:
Startup.cs:
services.AddDbContext<data_firstContext>(options =>
{
options.UseMySQL(Configuration.GetConnectionString("Database1"));
});
services.AddDbContext<data_secondContext>(options =>
{
options.UseMySQL(Configuration.GetConnectionString("Database2"));
});
FirstDbContext.cs:
public class FirstDbContext : DbContext
{
public DbSet<ViewModels.Tower> Towers { get; set; }
public DbSet<ViewModels.Motherboard> Motherboards { get; set; }
public FirstDbContext(DbContextOptions<FirstDbContext> options)
: base(options)
{
}
}
AdminController.cs:
[Authorize(Policy = "RequireAdminRole")]
public class AdminController : Controller
{
private readonly FirstDbContext _context;
public AdminController(FirstDbContext context)
{
_context = context;
}
public IActionResult Index()
{
return View();
}
public IActionResult Towers()
{
var model = _context.Towers.ToList();
return View(model);
}
}
Check this out: Entity Framework Core Using multiple DbContexts

no need of recompile when configuration changes in ASP.NET Core

I'm a beginner in ASP.NET Core, I was reading a book which says:
In ASP.NET Core, you finally get the ability to edit a file and have the configuration of your application automatically update,
without having to recompile or restart.
and it also says that when using strong typed setting with IOptions interface:
Registers the IOptions interface in the DI container as a singleton, with the final bound POCO object in the Value property.
So here is my question, if the implementation of IOptions is singleton, which means the app will get same instance of the service all the time. If thats the case, when the configuration files change, how can the app doesn't need to recompile to reflect the latest change? ( if the IOptions is singleton, the POCO object is always the same too)
If you use IOptions<T> interface then Value property will always return the same value set during configuration. In order to get updated values every time file has been changed you should inject IOptionsMonitor<T> or IOptionsSnapshot<T> interface.
Startup.cs
services.Configure<SomeOptions>(Configuration.GetSection("ConfigSection"));
TestController.cs (with IOptions)
public class TestController : Controller
{
private readonly IOptions<SomeOptions> _options;
public TestController(IOptions<SomeOptions> options)
{
_options = options;
}
[HttpGet]
public async Task<IActionResult> GetConfig()
{
return Json(_options.Value); //returns same value every time
}
}
TestController.cs (with IOptionsMonitor)
public class TestController : Controller
{
private readonly IOptionsMonitor<SomeOptions> _options;
public TestController(IOptionsMonitor<SomeOptions> options)
{
_options = options;
}
[HttpGet]
public async Task<IActionResult> GetConfig()
{
return Json(_options.CurrentValue); //returns recalculated value
}
}

Set EF ConnectionString at runtime from subdomain for multi-tenancy setup

Currently, my DbContext class has this code:
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
What I'd like to do, is inspect the subdomain and use that for the database name, so something like http://abc.mysite.com/ would use a connection string with database name abc.
But how do I manipulate the value of "DefaultConnection" in the constructor?
DBContext will take a name or a connection string in its constructor. That constructor is not usually exposed if you have a generated model.
You can use a partial class to expose that constructor:
public partial class DataEntities
{
public DataEntities(string connectionString) : base(connectionString)
{
}
}
I have done that before. My project was set up for DI with Castle Windsor and one of my IWindsorInstallers was DataAccessInstaller responsible for registering, among other classes like repositories, my database context and here is the relevant code:
container.Register(Component
.For<MyDatabaseContext>().Forward<DbContext>()
.ImplementedBy<MyDatabaseContext>()
.LifestylePerWebRequest()
.UsingFactoryMethod(context =>
{
return MyDatabaseContextFactory.Create(HttpContext.Current.Request.Url);
}));
You can have several connection strings set up in your web.config matching your domain.
My context factory implementation:
public static class MyDatabaseContextFactory
{
public static MyDatabaseContext Create(Uri uri)
{
return new MyDatabaseContext(uri.GetTopDomain());
}
}
If you just have a simple project and don't even have DI, you can still make use of a factory that finds out what the website use and instantiates a database context with the appropriate connection string.
Needless to say, your current database context constructor doesn't have to change.

ASP.NET vNext EF7 dbContext issues

I am starting a vNext project, and I'm having some issues kicking it off the ground. I have added a table to the ApplicationDbContext class, and it successfully created the table in the db (which in my case is in Azure). However, I can't seem to correctly instantiate a dbContext to use in my Controllers.
In my experience with previous ASP.NET EF projects, I could instantiate the ApplicationDbContext class without passing it any parameters, but in the case of vNext however, it seems to expect a number of things (IServiceProvider, and IOptionsAccessor<DbContextOptions>). I have tried creating a parameter-less constructor, but the App breaks due to not knowing what connection strings to use. My code is below -- as you see in the OnConfiguring(DbContextOptions options) override, I force the connection string in via the DbContextOptions, but that's obviously not ideal, and I feel like I'm just not understanding where those two IServiceProvider, and IOptionsAccessor parameters need to come from.
Thanks for any help!
namespace Project.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string CompanyName { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private static bool _created = false;
public DbSet<Business> Businesses { get; set; }
public ApplicationDbContext()
: base()
{
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
protected override void OnConfiguring(DbContextOptions options)
{
var configuration = new Configuration();
configuration.AddJsonFile("config.json");
configuration.AddEnvironmentVariables();
options.UseSqlServer(configuration.Get("Data:DefaultConnection:ConnectionString"));
}
public ApplicationDbContext(IServiceProvider serviceProvider, IOptionsAccessor<DbContextOptions> optionsAccessor)
: base(serviceProvider, optionsAccessor.Options)
{
// Create the database and schema if it doesn't exist
// This is a temporary workaround to create database until Entity Framework database migrations
// are supported in ASP.NET vNext
if (!_created)
{
Database.EnsureCreated();
_created = true;
}
}
}
}
IServiveProvider and IOptionAccessor are injected by the Dependency Injection
the ASP.Net Core DI has limitation, you cannot have more than one constructor.
Read this: http://blogs.msdn.com/b/webdev/archive/2014/06/17/dependency-injection-in-asp-net-vnext.aspx

Categories