MVC Multitenant Application Instance Scope on Context using Autofac? - c#

Hi everyone I am optimizing my Application and have a Question about it. I have a Design that looks like this:
ApplicationCore -> T4 from ApplicationDatabases Model Generator
ApplicationData -> Respositories which Access the Database Context in ApplicationCore
public class entityRepository<TEntity> : entityRepository<TEntity> where TEntity : class, IEntity
{
protected readonly DatabaseContext dbContext;
protected readonly IDbSet<TEntity> currentTableContext;
public entityRepository(DatabaseContext context)
{
this.DbContext = context;
currentTableContext = context.Set<TEntity>();
}
ApplicationDatabases -> Database created from Model Generator
ApplicationServices -> Services that creates a entityRepository
private readonly entityRepository<Users> _userRepository;
public userService(entityRepository<Users> userRepository)
{
_userRepository = userRepository;
}
What I would like to know if I am allowed to Register the entityRepository as InstancePerTenant in Autofac or should I use another Instance Scope.
builder.Register(context => new DatabaseContext()).InstancePerTenant();
builder.RegisterGeneric(typeof(entityRepository<>)).As(typeof(IentityRepository<>)).InstancePerTenant;
I am doing that at the moment and it works but I am not sure If I would get any Resources or similiar Problems later. I would also be happy if I get some suggestions.

IMHO, I have worked in a project that is multi-tenant, but does not use autofac. I have few points to put forth to you for a discussion
In case of a multi-tenant application and having the user and entity repositories loaded on memory for each tenant, seems like a bit of overhead
In case of a use case wherein a tenant wishes to view his child tenant's data, how can this be achieved
When there is an application load of 50 tenant's what happens to the parallelism and responsiveness to the load from the application point of view.
Kindly think through these use cases and the others that might come up after moving to production and share your thoughts.

Related

How to use Dependency Injection on a application class in .Net Core for an Entity Framework Pooled Context to achieve multi-tenant implementation?

I have implemented an application which makes use of .Net Core 3.1 and Entity Framework.
The application uses entity framework dbcontext pooling, utilizing the Pomelo mysql ef library.
services.AddDbContextPool<myDbContext>(
options => options
.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
mysqlOptions =>
{
mysqlOptions.MaxBatchSize(MySqlConfig.EfBatchSize);
mysqlOptions.EnableRetryOnFailure();
if (MySqlConfig.EfRetryOnFailure > 0)
{
mysqlOptions.EnableRetryOnFailure(MySqlConfig.EfRetryOnFailure, TimeSpan.FromSeconds(5), null);
}
}
).UseLoggerFactory(consoleLoggerFactory));
What is important to note is the use of the AddDbContextPool
Please see here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkservicecollectionextensions.adddbcontextpool?view=efcore-3.1
When using Context Pooling, your application is required to have a single constructor with DbContextOptions. ie: I cannot inject another object (in my case, I need a application service class for listing allowed tenants, and other claims related logic...) into this class, otherwise pooling cannot be used.
public myDbContext(DbContextOptions<myDbContext> options)
: base(options)
{
}
Another caveat is that I utilize the HttpContextAccessor to access the claims on the User which is authenticated, which include the Tenants allowed to be accessed by that user which of course is accessed via Dependency Injection as well.
The Authentication and Claims are retrieved via openid ultimately retrieving claims as Active Directory Groups, so I do not and would not have this info in the DB either as it originates from Active Directory...
The guts of this question is really, how can i access the HttpContext within my DbContext in order to implement a global filter for multi-tenant support in my application, and still use the DB Context Pooling.
I can achieve this by removing pooling, and make use of DI as normal via the constructor, but that is not what i'm after. I need to keep pooling, and implement the multi-tenant feature.
First, if your tenants are in different databases, DbContext pooling simply won't work. After a DbContext has been opened, it's connected to a single database. It's a minor performance feature, so it's not a great loss.
If you're setting query filters, you can probably make it work by simply not directly injecting a DbContext into your controllers, instead injecting a pre-configured bundle of services. And this also lets you centralize the code to rreconfigure the DbContext for the current tenant. EG:
public class ServiceContext
{
MyDbContext dbContext;
HttpContext httpContext;
IConfiguration config;
public ServiceContext(MyDbContext dbContext, IHttpContextAccessor httpContextAccessor, IConfiguration config)
{
this.dbContext = dbContext;
this.httpContext = httpContextAccessor.HttpContext;
this.config = config;
//use the httpContext to reconfigure the DbContext for single-tenant access
}
public HttpContext HttpContext { get => httpContext; set => throw new NotImplementedException(); }
public MyDbContext DbContext { get => dbContext; }
public IConfiguration Configuration { get => config; }
}

EF Core reusable DbContext

I'm trying to create a reusable base for future web applications made with asp net core.
I created a library that contains a BaseDbContext that inherit from IdentityDbContext:
public class BaseDbContext : IdentityDbContext<ApplicationUser>
{
public BaseDbContext(DbContextOptions options) : base(options)
{
}
}
Inside this library there are some services for login and creation of Users.
Everytime that I will be creating a new WebApplication I will reference the library and I will create a new DbContext like this:
public class ProjectDbContext : BaseDbContext
{
//some generics DBSET
public ProjectDbContext (DbContextOptions<ProjectDbContext> options) : base(options)
{
}
}
And in the startup:
services.AddDbContext<ProjectDbContext>(options =>
{
options.UseSqlServer(connection);
});
Since the service for the login and creation of users require a reference of BaseDbContext, I created a IDbContextFactory inside the base project that will be implemented by the main project like this:
public class ProjectContextFactory : IDbContextFactory
{
private readonly ProjectDbContext _projectDbContext;
public ProjectDbContextFactory(ProjectDbContext remDbContext)
{
_remDbContext = remDbContext;
}
public BaseDbContext GetBaseDbContext()
{
return _projectDbContext;
}
}
This factory will be used inside the base project to get a reference to the BaseDbContext.
Is this a good thing to do? Can this create some kind of problems?
In general, no, this is not a good thing to do.
that will contains the entities that will be used for all web applications
If there's entities that are common to all projects, then those should be factored out completely. In other words, you'd have one project (your base project) with a context like UserContext, which will have your User and Credential entities, and then every other project would have its own separate context that deals with just what it needs. If the other application(s) need to access users, they'd either do so via an instance of UserContext or, better, through a service, such as an API.
That said, it sounds like you're rolling your own auth, which you should emphatically not do. Use Identity. And, if you need to share that between applications, you need a centralized auth provider, such as Identity Server.

How can I add a dbContext after Startup in .Net Core?

I am using .Net Core, using the built-in dependency injection. In my login screen, I need the user to also choose a departmental database - we have different databases with the same structure to use the same application with different data. However, I can't figure out how to add/modify the dbContext that late. Startup.cs has the DI, but I don't know which connection string to read from the config until the user has chosen the department. It is a small database, and the company is not concerned about the management of the duplicate databases.
How can I add the service late
services.AddDbContext<my_accountingContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("CorrectDepartmentConfig")));
when I actually know what CorrectDepartmentConfig is?
Or, if that can't be done, how can I do a smelly change of the my_accountingContext after Startup.cs?
You can use an implementation factory overload of IServiceCollection in ConfigureServices method form Startup class:
//First register a custom made db context provider
services.AddTransient<ApplicationDbContextFactory>();
//Then use implementation factory to get the one you need
services.AddTransient(provider => provider.GetService<ApplicationDbContextFactory>().CreateApplicationDbContext());
The implementation of CreateApplicationDbContext depends on your specific needs, but a base implementation should look like the following:
public ApplicationDbContext CreateApplicationDbContext(){
//TODO Something clever to create correct ApplicationDbContext with ConnectionString you need.
}
After this implementation, you can inject the correct ApplicationDbContext in your controller, action...
public MyController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public IActionResult([FromServices] ApplicationDbContext dbContext){
}
You can always set the connection string from inside the protected OnConfiguring method. You can get access to the IConfiguration instance from there (the DbContext class has a service locator, Instance property), retrieve the connection string, and then call UseMySql extension method with the appropriate connection.
Something like this:
protected virtual void OnConfiguring(DbContextOptionsBuilder builder)
{
var configuration = (this as IInfrastructure<IServiceProvider>).GetService<IConfiguration>();
var connectionString = configuration.GetConnectionString("<name>");
builder.UseMySql(connectionString);
base.OnConfiguring(builder);
}
For the strongly-typed version of GetService do not forget to reference namespace Microsoft.Extensions.DependencyInjection.

Switch database using routes

Here's my scenario:
I Have a single app, but I need to switch the database connection by route.
Example:
switch(route)
{
case(URL/A):
{
USE DATABASE 1
}
case(URL/B):
{
USE DATABASE 2
}
DEFAULT:
USE DATABASE DEFAULT
}
Is it possible?
Since you're using ASP.NET MVC, your routes depends on your controllers. Then you can imagine having ControllerA using DatabaseA and ControllerB using DatabaseB.
To use multiple database connections, you need a connection string for each one of them.
I would use the following pieces of code to inject instances of DbContextOptionsBuilder inside of Startup.ConfigureServices()
var ContextAOptionsBuilder = new DbContextOptionsBuilder<ContextA>();
var ContextBOptionsBuilder = new DbContextOptionsBuilder<ContextB>();
Then you can configure your builders this way (depending on your parameters)
ContextAOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("ContextAConnectionString"), builder =>
{
builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null);
});
ContextAOptionsBuilder.EnableSensitiveDataLogging();
Then you can inject them as singletons this way :
services.AddSingleton(typeof(DbContextOptionsBuilder<ContextA>),ContextAOptionsBuilder);
You can use a BaseController, whose constructor parameters can access to services this way :
public BaseController(IConfiguration configuration, IMemoryCache memoryCache,
IHttpContextAccessor contextAccessor,
DbContextOptionsBuilder<ContextA> ContextAOptionsBuilder,
DbContextOptionsBuilder<ContextB> ContextBOptionsBuilder){}
Of course, ControllerA and ControllerB being heir classes of BaseController, you can access desired builder quite simply.
public ControllerA(IConfiguration configuration,
IMemoryCache cache,
IHttpContextAccessor contextAccessor,
DbContextOptionsBuilder<ContextA> ContextAOptionsBuilder,
DbContextOptionsBuilder<ContextB> ContextBOptionsBuilder)
:base(configuration, cache, contextAccessor, ContextAOptionsBuilder,ContextBOptionsBuilder)
{
//Create your DbContext using the builder
}
This way you can use one, the other, or both database to build your context
A simpler way would have been injecting your configuration file and building your context from it's content but ppumkin's comment suggested it's a bad idea to do this at a controller level.
This solution is working for me in an ASP.NET Core MVC application, I am still learning the framework but maybe my answer gave you precisions about multiple DbContexts.
You can create 3 connection string also 3 data access Classes. First of your class uses for example DropCreateDatabaseIfModelChanges others use CreateDatabaseIfNotExists. When you call first class your database creates when you need others there will no need recreate it.
Register your context (as scoped, per request) and use factory method for dynamically creating context with specified connection string based on current route (which should be available from HttpContext or something similar). If the databases schemas are same and just data is different this should work easily. I can't provide a snippet for you because it's mostly depends on what DI framework you have.

Initializing DBContext in Asp.Net Core

I want to use Repository & Unit Of Work in my project.
But in ASP.NET MVC when we want use DBContext to use this code
MyDbContext db=new MyDbContext();
but in ASP.NET Core when write this code it want an argument
because use this code in DbContext Class
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
Error:
what is the problem?
You can initilize your DB context like this:
var optionBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionBuilder.UseSqlServer("Server=localhost;...");
var context = new MyDbContext(optionBuilder.Options);
Previous code is configuring the options to the connection and then creating a MyDbContext using those options.
If you want to use a InMemoryDatabase for unit testing for example you can change it to this:
var optionBuilder = new DbContextOptionsBuilder<MyDbContext>().UseInMemoryDatabase("testindDB")`;
public MyDbContext(DbContextOptions<MyDbContext> options)
You have not empty constructor in your MyDbContext class, So you should do pass parameter DbContextOptions<MyDbContext> options in constructor.
For example you can see it -> link1
You shouldn't be instantiating the DbContext, you should be requesting it as a constructor argument to your repository. Then your IOC container will provide the DbContext at runtime. This ensures that you can use the same DbContext throughout a given ASP.NET web request, which will prevent a host of problems you're otherwise likely to encounter.
You can see an example of a generic repository here:
http://deviq.com/repository-pattern/
You also typically don't need a separate Unit of Work in ASP.NET applications (but sometimes you do). This is because your requests should be very small and you should be able to do most of the work in a single controller or service, and then simply save through the repository. It's not that you never need UoW, but it's less necessary than in a thick client scenario (e.g. windows app or service).
You can try it: In your class UnitOfWork
private MyDBContext _context;
public UnitOfWork(MyDBContext context)
{
_context = context;
}
In your controller:
private UnitOfWork _unitOfWork;
public MoviesController(MyDBContext context)
{
_unitOfWork = new UnitOfWork(context);
}

Categories

Resources