How to get multiple implementations of the same service interface each in separate scopes

Viewed 229

I have an asp.net core 2.2 web application. There is an Interface which is implemented by multiple classes.

services.AddTransient<IHandler, HandlerA>();
services.AddTransient<IHandler, HandlerB>();

Each implementation of IHandler injects scoped EF DbContext and if the same DbContext is shared between different threads, then periodically a floating exception will occur when trying to access the same entity. So I get each handler in a separate scope.

using (var scope = _serviceProvider.CreateScope())
{
    var handlers = scope.ServiceProvider.GetServices<IHandler>();
    await Task.WhenAll(handlers.Select(async h =>
    {
        using var internalScope = scope.ServiceProvider.CreateScope();
        var handler = internalScope.ServiceProvider
            .GetServices<IHandler>()
            .First(f => f.Name == h.Name);
        await handler.Handle(cancellationToken);
    }));
}

This solution seems to work, but I'm not sure if it's optimal. Maybe there is a better way to get multiple implementations of the same service interface in separate scopes?

1 Answers

You do not need to access same entity using different DBContext. Define services and inject in controllers explained above link. Each service will access DB using same db context through Manager Classes.

You can use service this way as you seeking best practices. And use dependency injection to access the services from controllers.

  1. Define Service, Domain classes and interfaces.
public class Service : Attribute 
{
}

//Domain Classes
public class Entity
{
  string str {get; set;}
  int numb {get; set;}
}

//DBContext
 public class DbContext : IdentityDbContext
 {
        public DbContext(DbContextOptions<DbContext> options)
            : base(options)
        {

        }
        public DbSet<Entity> Entities { set; get; }
}

//Interfaces
interface IHandler
{
    string method1(Entity entity);
    int method2();
}
  1. Implement Interfaces using manager classes
public class HandlerA: IHandler
{
    private readonly DbContext _dbContext;
    public HandlerA(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    string method1(Entity entity)
    {
         //access db or business stuff
         _dbContext.Entities.Add(entity);
         _dbContext.SaveChanges();
    }
    int method2(){}
}

public class HandlerB: IHandler
{
    string method1(Entity entity)
    {
         //access db or business stuffs
         _dbContext.Entities.Remove(entity);
         _dbContext.SaveChanges();
    }

    int method2(){}
    int method3(){}
}

//Services
[Service]
public class DoService1Stuff()
{
     private readonly HandlerA _handlerA;
     public DoService1Stuff(HandlerA handlerA)
     {
         _handlerA= handlerA;
     }
     
     //Implement your task
     public Do(Entity entity)
     {
         _handlerA.method1(entity);
     }
}
[Service]
public class DoService2Stuff()
{
     private readonly HandlerB _handlerB;
     public DoService2Stuff(HandlerB handlerB)
     {
         _handlerB= handlerB;
     }
     
     //Implement your task
     public Do(Entity entity)
     {
         _handlerA.method1(entity);
     }
}
  1. Register services through dependency injection in startup.cs
services.AddTransient<IHandler, HandlerA>();
services.AddTransient<IHandler, HandlerB>();
  1. Access services in controllers
[HttpPost]
public async Task<IActionResult> DoStuff1([FromServicec] DoService1Stuff doService1Stuff)
{
    var entity= new Entity
    {
        str="hello",
        numb=2020;
    };

    return Ok(doService1Stuff.Do(entity))
}
//Implement second service as needed.
Related