I have one master DB context that has a unique schema.
I have 100+ databases that have identical schema and exist on the same server.
A user authenticates against the master DB. After auth, depending on the user, the rest of the queries target a specific DB from my list of 100+ identical schema DBs.
In ASP .NET Core + EF Core, how do I register a factory or context that I can specify a connection string for post-injection (based on a provided value from the auth'd user)?
I'm playing with IDbContextFactory<MyDbContext> and a few other options, but I can't seem to find any clear examples of providing a connection string or connection property after the context has been injected.
Just create your own factory.
public interface ITentantDbContextFactory<T>
{
T Create(string tenantId);
}
public class AppTenantDbContextFactory : ITenantDbContextFactory<AppDbContext>
{
public AppDbContext Create(string tenantId)
{
// do some validations on tenantId to prevent users from
// injecting arbitrary strings into the connection string
var builder = new DbContextOptionsBuilder<AppDbContext>();
// you can also load it from an injected option class
builder.UseSqlServer($"Server=MYSERVERHERE;Database={tenantId};Trusted_Connection=True;");
return new AppDbContext(builder.Options);
}
}
Then inject the factory where ever you need it. Downside is that you have to manage the live time of the DbContext and dispose it.
You could also implement IDisposable interface on your factory and hold a list of references and dispose these when Dispose() method is called (which will be done at the end of the request, if your factory is registered as transient or scoped.
Also Unit of Work pattern comes helpful here, if you need to access more than one DbContext and use the tenantId (or whatever its called in your project) as key in a Dictionary<string, AppDbContext>, so you will get the same instance when calling factory.Create("tenantA"). Then add a SaveChanges method which will call SaveChanges of all instantiated AppDbContext instances
Related
I recently came across AddPooledDbContextFactory concept as part of my NET 5 self-education piece and am keen to implement it properly. However, I am not sure how to use it with generics that I generally use.
Example of my current setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestDbContext>(
(s, o) => o.UseNpgsql(Configuration.GetConnectionString("DatabaseConnection"))
.UseLoggerFactory(s.GetRequiredService<ILoggerFactory>()));
// other code //
}
my repository generic:
public class Repository<T> : IRepository<T> where T
{
private readonly TestDbContext _dbContext;
public Repository(TestDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Create(T entity)
{
await _dbContext.Set<T>().AddAsync(entity);
await _dbContext.SaveChangesAsync();
}
// other methods //
}
this is invoked in following manner as example:
public class WeatherForecastController : ControllerBase
{
private readonly IRepository<Test> testRepo;
public WeatherForecastController(IRepository<Test> testRepo)
{
this.testRepo= testRepo;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> GetAsync()
{
await testRepo.Create(new Test { Name = "Superman" });
// other code
}
}
I would like to convert this to use the new AddPooledDbContextFactory concept but cannot find enough documentation to figure out how to do this.
Atm only thing that comes to mind is using statements at each method but that doesn't make sense.
Any advice on this?
Documentation is not yet complete and is in progress, you track this issue
https://github.com/dotnet/EntityFramework.Docs/issues/2523
You can also a look at the tests for AddPooledDbContextFactory to see how to register DbContext with
https://github.com/dotnet/efcore/search?q=AddPooledDbContextFactory
for example to register DbContext:
services.AddPooledDbContextFactory<TContext>(ob =>
ob.UseSqlServer("ConnectionString").EnableServiceProviderCaching(false), poolSize: 32)
Then in your class, inject an IDbContextFactory<TContext> and use it like this:
using(var context = _factory.CreateDbContext())
{
var orders = await context.Orders.Where(o => o.Id > 3).ToListAsync();
}
According to this post:
Note that the DbContext instances created in this way are not managed
by the application's service provider and therefore must be disposed
by the application
You can also check out this post to see how to use IDbContextFactory:
https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-5.0
#Aeseir your code looks good to me. You are following best practices and you don't need to change it.
You are using the Repository Pattern, so your Repository class has all of your query logic which helps create loosely coupled and maintainable code.
In your ConfigureServices, calling: services.AddDbContext<TestDbContext>() registers TestDbContext with Scoped service lifetime. This is the way that DbContext is designed to work, and it will also work well with ASP.NET controllers, since they have a Scoped lifetime as well.
You did not show your code for registering IRepository, but that service lifetime should be Scoped as well. Btw, you can tell BuildServiceProvider() to validate scope registrations:
builder.Services.BuildServiceProvider(validateScopes: true);
Since DbContext is designed to have a Scoped service lifetime, and since your IRepository and Controller services are Scoped as well, every request gets brand new:
Controller
IRepository
DbContext
Those services are used for the request and then Diposed. This is how ASP.NET is intended to work.
Apparently at some point, DbContext pooling has been introduced to improve performance. In this case, EF Core manages a pool of context instances for you and resets them after each request. This can improve performance, although in some situations, the benefit might be small. See MSDN documentation for more details.
I think for use with ASP.NET controllers (i.e. the code you posted above) all you need to do to take advantage of EF Core context pooling is call AddDbContextPool():
builder.Services.AddDbContextPool<ApplicationDbContext>(/* ... */);
However, if you needed to use DbContext in services registered with Singleton lifetime, then the pattern above would not work well. Because when a Scoped service gets used in a Singleton service, the Scoped service is essentially a Singleton. Each request would not get a new DbContext, nor a reset one from the pool. (See QUESTION below.)
In that case, you might want to use the DbContext factory pattern instead:
builder.Services.AddDbContextFactory<ApplicationDbContext>(/* ... */);
Or, if you want to use context pooling with a factory pattern:
builder.Services.AddPooledDbContextFactory<ApplicationDbContext>(/* ... */);
The DbContextFactory can then be used in other services through constructor injection. For example:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
The injected factory can then be used to construct DbContext instances in the controller code. For example:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Keep in mind that when you call CreateDbContext(), context instances are not managed by the service provider and therefore must be disposed by the application. Hence you need to Dispose of them yourself, such as in the example above which does so with the using statement.
QUESTION
I am doing my best to understand this stuff and explain it, but I might be wrong, so please call out an inaccuracies in my post.
When using AddDbContextPool(), does the DbContext get registered as a Singleton or Scoped?
I found in MSDN documentation that it's effectively registered as a Singleton:
Context pooling works by reusing the same context instance across requests; this means that it's effectively registered as a Singleton, and the same instance is reused across multiple requests (or DI scopes). This means that special care must be taken when the context involves any state that may change between requests.
However, I have found that if AddDbContextPool() is used along with true for validatedScopes:
builder.Services.BuildServiceProvider(validateScopes: true)
When DbContext is consumed from another service which is registered as a Singleton, the following exception is thrown:
System.InvalidOperationException: 'Cannot consume scoped service 'ApplicationDbContext' from singleton 'IRepository'.'
Hence why I stated above that DbContext still gets Scoped service lifetime.
tl;dr How can I use Entity Framework in a multithreaded .NET Core API application even though DbContext is not threadsafe?
Context
I am working on a .NET Core API app exposing several RESTful interfaces that access the database and read data from it, while at the same time running several TimedHostedServices as background working threads that poll data regularly from other webservices and store them into the database.
I am aware of the fact that DbContext is not threadsafe. I read a lot of docs, blog Posts and answers here on Stackoverflow, and I could find a lot of (partly contradictory) answers for this but no real "best practice" when also working with DI.
Things I tried
Using the default ServiceLifetime.Scoped via the AddDbContext extension method results in exceptions due to race conditions.
I don't want to work with locks (e.g. Semaphore), as the obvious downsides are:
the code is polluted with locks and try/catch/finally for safely releasing the locks
it doesn't really seem 'robust', i.e. when I forget to lock a region that accesses the DbContext.
it seems redundant and 'unnatural' to artificially syncronize db access in the app when working with a database that also handles concurrent connections and access
Not injecting MyDbContext but DbContextOptions<MyDbContext> instead, building the context only when I need to access the db, using a using statement to immediatelly dispose it after the read/write seems like a lot of resource usage overhead and unnecessarily many connection opening/closings.
Question
I am really puzzled: how can this be achived?
I don't think my usecase is super special - populating the db from a Background worker and querying it from the web API layer - so there should be a meaningful way of doing this with ef core.
Thanks a lot!
You should create a scope whenever your TimedHostedServices triggers.
Inject the service provider in your constructor:
public MyServiceService(IServiceProvider services)
{
_services = services;
}
and then create a scope whenever the task triggers
using (var scope = _services.CreateScope())
{
var anotherService = scope.ServiceProvider.GetRequiredService<AnotherService>();
anotherService.Something();
}
A more complete example is available in the doc
Another approach to create own DbContextFactory and instantiate new instance for every query.
public class DbContextFactory
{
public YourDbContext Create()
{
var options = new DbContextOptionsBuilder<YourDbContext>()
.UseSqlServer(_connectionString)
.Options;
return new YourDbContext(options);
}
}
Usage
public class Service
{
private readonly DbContextFactory _dbContextFactory;
public Service(DbContextFactory dbContextFactory)
=> _dbContextFactory = dbContextFactory;
public void Execute()
{
using (var context = _dbContextFactory.Create())
{
// use context
}
}
}
With factory you don't need to worry about scopes anymore, and make your code free of ASP.NET Core dependencies.
You will be able to execute queries asynchronously, which not possible with scoped DbContext without workarounds.
You always be confident about what data saved when calling .SaveChanges(), where with scoped DbContext there are possibilities that some entity were changed in other class.
I noticed the project template for MVC when using individual user accounts puts a few objects in the current Owin context (in App_Start/Startup.Auth.cs):
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
It looks like this is to access the database for Identity features. My understanding is that a single instance of ApplicationDbContext is created per request and re-used through the entire pipeline. Would it be beneficial to do the same with my own entity framework DbContexts?
For example I created a new file in App_Start/Startup.Data.cs:
public partial class Startup
{
public void ConfigureData(IAppBuilder app)
{
app.CreatePerOwinContext(CreateParkingEntities);
}
protected ParkingEntities CreateParkingEntities()
{
return new ParkingEntities();
}
}
Then in Startup.cs:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
ConfigureData(app);
}
}
Then I can use the context in my controllers:
private ParkingEntities _db;
public ParkingEntities DbContext
{
get
{
return _db ?? HttpContext.GetOwinContext().Get<ParkingEntities>();
}
private set
{
_db = value;
}
}
I would think if this was standard practice, the entity framework would have some scaffolding for this, but it just creates an instance at the controller level. Is it safe to assume that if DbContext is only accessed from that controller then it would be functionally equivalent to the above implementation and placing it in the Owin pipeline is overkill?
I suppose another use of this approach is a single initialization point for the DbContext, if additional setup is needed.
DbContext is designed to be lightweight, so there isn't much cost to creating a new instance every time you need one. The main costs are re-creating the connection and garbage-collecting all of the instances you are creating.
Note that if you use the Find() method on the DbSets, it will cache the result, so next time you ask for it, it doesn't need to go to the database. If you re-use the same context, you can take advantage of that cache, but if you create a new context, you lose the cache.
If you store the DbContext in an instance member of the controller, then it will only get used for one request, since a new instance of the controller will be created for each request. Just make sure you DON'T put it in a static member - I expect that you would get all kinds of race conditions if you do that.
The main approach to use EF db-context is simple: connect to the db-context, get your data and forget about this db-context object. EF context is IDisposable and closes the connection after dispose (don't forget about using(){}). It means, that you can create EF data-context anywhere in your application when it needs a db-connection. You can create new db-context right inside the controller-action if they are using your db. Everything depends on what is the architecture of your application. If you are sure, that all requests to your app will need many db-operations, I think your solution with db-context per owin-context can be useful.
I'm in a situation where the classic functionality of vnext's DI container is not enough to provide me with the correct functionality. Let's say I have a DataService that gets data from a database like this:
public class DataService : IDataService, IDisposable {
public List<MyObject> GetMyObjects()
{
// do something to fetch the data...
return myObjects;
}
}
I can then register this service in the DI container during the configuration phase in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDataService), typeof(DataService));
}
This ensures the correct lifecylce of the service (one per request scope), however, I need the service to access a different database when a different request is made. For simplicity reasons, let's say the following scenario applies:
when a request to my Web API is made, the DataService will access the currently logged in user, which contains a claim called Database which contains the information which database to use.
the DataService is then instantiated with the correct database connection.
In order to get the second step to work, I have created a constructor for the DataService like this:
public DataService(IHttpContextAccessor accessor)
{
// get the information from HttpContext
var currentUser = accessor.HttpContext.User;
var databaseClaim = currentUser.Claims.SingleOrDefault(c => c.Type.Equals("Database"));
if (databaseClaim != null)
{
var databaseId = databaseClaim.Value;
// and use this information to create the correct database connection
this.database = new Database(databaseId);
}
}
By using the currently logged in user and his claims, I can ensure that my own authentication middleware takes care of providing the necessary information to prevent attackers from trying to access the wrong database.
Of course adding the IDisposable implementation is required to cleanup any database connections (and gets called correctly using the scope lifecycle).
I can then inject the DataService into a controller like this
public MyController : Controller
{
private IDataService dataService;
public MyController(IDataService dataService)
{
this.dataService = dataService;
}
}
This all works fine so far.
My questions now are:
Is there another way to create the instance other than using the constructor of the DataService? Maybe accessing the object the IServiceCollection provides in a different place other than during the configration phase which runs only once? Maybe using my own OWIN middleware?
Is this method really safe? Could two requests made at the same time accidentally end up with the DataServiceintended for the other request and therefore end up giving out the wrong data?
What you have is fine.
Is there another way to create the instance other than using the constructor of the DataService? Maybe accessing the object the IServiceCollection provides in a different place other than during the configration phase which runs only once? Maybe using my own OWIN middleware?
Not really. You can use delegate registration but it's the same problem.
Is this method really safe?
Yes
Could two requests made at the same time accidentally end up with the DataServiceintended for the other request and therefore end up giving out the wrong data?
Nope. The IHttpContextAcessor uses AsyncLocal (http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html) to provide access to the "current" http context.
In my MVC application, I user SignalR for communication between users. Basically, a client calls a method on the hub, which calls a method on a repository, which then saves the message to the database and the hub notifies the other client of the new message.
I had used the GetOwinContext() method during these calls from the client to get the current instance of UserManager and ApplicationDbContext, by using the GetUserManager<UserManager>() and Get<ApplicationDbcontex>() extension methods, respectively. However, I have noticed that calls from the same connection use the same context, which is, obviously, not a very good thing. I went ahead and changed my repository so it is like this now:
public XyzRepository() //constructor
{
db = ApplicationDbContext.Create(); //static method that generates a new instance
}
private ApplicatonDbContext db { get; set; }
private UserManager UserManager
{
get
{
return new UserManager(new UserStore<ApplicationUser>(db)); //returns a new UserManager using the context that is used by this instance of the repository
}
}
Since I reference the ApplicationUser objects using the UserManager (using FindByIdAsync(), etc, depending on the design), it is extremely important to use the context I currently work with for the UserStore of the UserManager's current instance. The repository is created once per request, which seems to apply to each SignalR calls as intended. While I have experienced no problems with this design so far, after reading about the issue (in this article), particularly this line:
"In the current approach, if there are two instances of the UserManager in the request that work on the same user, they would be working with two different instances of the user object.", I decided to ask the community:
Question: what is the preferred way to use ASP.NET Identity's UserManager class with SignalR, if it is imperative that I use the same instance of DbContext for my repository's methods that the UserManager's UserStore uses?
I think the preferred way is to use an Inversion of Control container and constructor-inject dependencies with some kind of lifetime scope. Here is another question that you might want to look into:
Using Simple Injector with SignalR
It is preferable that your DbContext instance live as long as the current web request. IoC containers have facilities that let you register DbContext instances with per web request lifetimes, but you need to set up the IoC container so that it can manage the construction of the Hub classes to achieve this. Some IoC containers (like SimpleInjector) will also automatically dispose of the DbContext at the end of the web request for you, so you don't need to wrap anything in a using block.
As for the UserManager, XyzRepository, etc, I think those can also have per-web-request lifetime, or even transient lifetimes. Ultimately, I don't see why you wouldn't be able to achieve something like this:
public class MyXyzHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly MessageRepository _messageRepository;
public MyXyzHub(UserManager<ApplicationUser> userManager,
MessageRepository messageRepository)
{
_userManager = userManager;
_messageRepository= messageRepository;
}
public void sendMessage(string message)
{
var user = _userManager.FindByIdAsync(...
_messageRepository.CreateAndSave(new Message
{
Content = message, UserId = user.Id
});
Clients.All.receiveMessage(message, user.Name);
}
}
If you wire up your IoC container the right way, then every time the Hub is constructed, it should reuse the same ApplicationDbContext instance for the current web request. Also with your current code, it looks like XyzRepository is never disposing of your ApplicationDbContext, which is another problem that an IoC container can help you out with.