Repository pattern sharing changes across scoped instances - c#

I am using a pretty standard generic repository pattern (for example https://codewithmukesh.com/blog/repository-pattern-in-aspnet-core/#What_would_happen_if_we_didnt_have_an_UnitOfWork_Abstraction)
In program.cs I define my DB context and generic repository services as scoped.
...
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(configuration.GetConnectionString(connectionName)) ;
services.AddScoped(typeof(IGenericRepository<,>), typeof(GenericRepository<,>));
...
In a worker service I create two scoped instances during code execution;
using (var serviceScope = _serviceProvider.CreateScope())
{
var personDataService = serviceScope.ServiceProvider.GetRequiredService<IGenericRepository<Person, MyDbContext>>();
var auditLogDataService = serviceScope.ServiceProvider.GetRequiredService<IGenericRepository<AuditLog, MyDbContext>>();
...
}
When I make a call that generates an SQL exception on the first service I want to log the error in the second service, for example;
try {
await personDataService.InsertAsync(myNewPerson);
}
catch (Exception ex)
{
var newAuditLog = new AuditLog("Exception occurred inserting a new user", ex);
await auditLogDataService.InsertAsync(newAuditLog);
}
However, when personDataService generates a SQLException, for example;
SqlException: Cannot insert the value NULL into column 'Name'"
then the catch block triggers and I get the same error again when I run InsertAsync() on the 2nd auditLogDataService service.
SqlException: Cannot insert the value NULL into column 'Name'"
It appears that the changes from the first service are also in the second service. I'm assuming that MyDbContext is shared.
How do I create an independent instance of auditLogDataService so I can save the 2nd change without the first?

You probably use services.AddDbContext<YourDbContext>(), which makes it a scoped service by default.
This means that within one scope (which you create), you get the same instance of the DbContext every time you or another service requests it.
Mark it as transient instead, to get a new instance every time you request one. See Configuring Dbcontext as Transient:
services.AddDbContext<ApplicationDbContext>(options =>
options.Use(...),
ServiceLifetime.Transient);
Meta-commentary: please don't use repository patterns with Entity Framework. EF already exposes a repository through DbSet<T>. How are you going to support Include()s? Projections (Select())? Groupings (GroupBy)?

Could you try sending the DBContext object via constructor call for each and every repositories? For instance, whenever generic repository is used for any class Type, make it's constructor call and pass the DBContext object right away. So, every time the object of repository defined for that interface is called, you get the current DBContext.

You could inject a dbContextFactory into your repository and make a new DbContext from your factory there.

Related

NET 5 and EF: how to use AddPooledDbContextFactory in liu of DbContext in services

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.

Unable to access services from DI on delegate in startup Configure?

I am trying to add some items to the piranha sitemap using the delegate method OnGenerateSitemaps.
In this method I am calling to a service that gets data from entity framework context and then caches it. Whenever I try to use this service in the delegate-method I get a error that the dbContext has already been disposed.
System.AggregateException: 'One or more errors occurred. (Cannot
access a disposed context instance. A common cause of this error is
disposing a context instance that was resolved from dependency
injection and then later trying to use the same context instance
elsewhere in your application. This may occur if you are calling
'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances.
I've tried making the service sync instead of async, I've tried awaiting the result and running the task sync, none of which works.
Any ideas on how to use my service in this delegate in Configure on startup?
services.AddScoped<ICachedSitemapService, CachedSitemapService>();
In startup I inject the service, which is scoped.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api, ICachedSitemapService cachedSitemapService)
{
App.Hooks.OnGenerateSitemap += (sitemap) =>
{
var items = await cachedSitemapService.GetCachedClinics().Result;
sitemap.AddRange(items);
return sitemap;
};
}
The service that is called is DbContext to get items:
private async Task<IEnumerable<SitemapItem>> GetSitemapClinics()
{
var spec = new ClinicSitemapSpecification();
//This throws error
var allClinics = await _clinicRepo.ListAsync(spec);
//code shortened for brevity, but returns a list.
}
I've tried below without any luck.
var items = await cachedSitemapService.GetCachedClinics().GetAwaiter().GetResult();
sitemap.AddRange(items);
We're (the Piranha team) planning on redesigning the hook system in version 10 (has to be a major version as it will break backward compatibility) to provide DI functionality in all hooks. However, in the meantime, the following should fix your issues.
using (var scope = app.ApplicationServices.CreateScope())
{
var service = scope.ServiceProvider.GetService< ICachedSitemapService>();
}
Since your service is registered to be scoped it can't be resolved directly from the root provider (like the error states). When resolving the service from a controller you are in the request scope, so the solution here is to just create a scope that will allow you to resolve it.

Connect to database using dependency injection

I am trying to learn how to use dependency injection, but I have some trouble when it comes to my database. This is my process so far:
I have an MVC project where the controllers use different repositories from my classlibrary.
All repositories use the same database.
At first, I used SimpleInjector to register the Repositories Application_start method:
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
var client = new GraphClient(uri, username, password);
container.Register<IRepoA>(() => new RepoA(client);
container.Register<IRepoB>(() => new RepoB(client);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
And in every method, I did like this:
client.Connect();
client.performSomeQuery();
client.Dispose();
This works, but it means that I am reconnecting to the database every single time I call a method.
To avoid this a moved the connect-call to here:
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
var client = new GraphClient(uri, username, password);
client.Connect();
container.Register<IRepoA>(() => new RepoA(client);
container.Register<IRepoB>(() => new RepoB(client);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
But then I never get my connection disposed.
I thought now is to register my databaseclient;
container.RegisterSingleton(() =>
{
var client = new GraphClient(uri, username, password);
client.Connect();
return client;
});
And then inject it like this:
container.Register<IRepoA>(() => new RepoA(container.GetInstance<GraphClient>()));
Is this the correct way to do it?
Is it corretly understood that the connection will get disposed by the end of the containers lifetime?
I do get a "Implicitly captured closure: container" when I register the client.
there is more than one way to do things ... so asking for THE correct way might get you on the wrong path sometimes ...
but here is what i'd do in your case ...
i would introduce a pattern called unit-of-work ... think of it as a business transaction
you open a unit of work and within its lifetime you perform various DB interactions, possibly all within one database transaction. All those interactions may spread across different repositories. if your whole batch of interactions is done without errors that would require a rollback, you declare the unit of work complete and leave its scope (scope as in a using(...) scope)
if there is an error you do not declare it complete before its lifetime ends ...
on the end of lifetime of your unit of work, you can either commit or roll back all underlaying db transactions (usually it's only one) depending on the fact if complete was declared or not
this unit of work object usually also holds my db connection object(s) and provides connections to repositories
again with dependency injection, you can have factory methods that provide different db connections based on the interface the repository requests during instantiation ...
usually the first repo that needs a connection causes the factory to create one and open it (optionally you can have different connections based on the used repos) while the second repo that asks for a connection gets a connection that has been created and opened before ...
the end of the unit of work (see IDisposable) also means the end of the connectionfactory ... end of connectionfactory means the end of open connections and repos ... since the later are instantiated insied of a using block, and based on the used resource, they should never leave said block

EF Core inject dbcontext with many potential connection strings

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

How to dynamically create and inject services in ASP.NET 5?

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.

Categories