Home > Back-end >  Repository pattern sharing changes across scoped instances
Repository pattern sharing changes across scoped instances

Time:08-20

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?

CodePudding user response:

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)?

CodePudding user response:

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.

CodePudding user response:

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

  • Related