3
votes

With EF Core, DbContext is registered as Scoped by EF service extension. This is desirable because DbContext is not thread-safe and therefore it should be created per request.

ServiceStack IOC treats any Scoped registration in Startup as singleton, which contradicts with the point above.

One possible solution is to not use EF Core's service extension, but that seems to bring a lot of boilerplate code and reduce maintainability. Is there any better way?

--

UPDATE

I'd like to provide sample code for clarity

I added a private Guid to the DbContext class so that I can tell whether we have the new instance.

public class BloggingContext : DbContext
{
    private readonly Guid _instance;

    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { 
        _instance = Guid.NewGuid();
    }

    public DbSet<Blog> Blogs { get; set; }
}

With .NET Core MVC, the controller code looks like

public class BlogsController : Controller
{
    private readonly BloggingContext _context;

    public BlogsController(BloggingContext context) 
    {
        _context = context;
    }

    // skip for readability
}

For each request hitting the controller, the _instance inside BloggingContext returns an unique value. However, when using within a ServiceStack service, _instance always returns the same value.

public class BlogService : ServiceStack.Service
{
    private readonly BloggingContext _context;

    public BlogService(BloggingContext context) 
    {
        _context = context;
    }

    // skip for readability
}

This behaviour is consistent with ServiceStack documentation about .NET Core Container Adapter that scoped dependencies registered in .NET Core Startup is singleton within ServiceStack. However, it is not desirable because we want DbContext to be created per request.

My solution is that I move the DbContext registration into AppHost code as below

public override void Configure(Container container)
{
    container.AddScoped(c => 
    {
        var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
        optionsBuilder.UseSqlServer(connection);
        return new BloggingContext(optionsBuilder.Options);
    });     
}

This code works as I expect. Every instance of BloggingContext injected into my BlogService is now unique. However, I find myself unable to use any service collection extension which is very handy in .Net Core Startup anymore. For example, I want to use Entity Framework Unit Of Work and I couldn't call

services
    .AddUnitOfWork<BloggingContext>();

Instead, I have to wire up all dependencies of that library myself like

public override void Configure(Container container)
{
    container.AddScoped(c => 
    {
        var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
        optionsBuilder.UseSqlServer(connection);
        return new BloggingContext(optionsBuilder.Options);
    });     
    container.AddScoped<IRepositoryFactory, UnitOfWork<BloggingContext>>();
    container.AddScoped<IUnitOfWork, UnitOfWork<BloggingContext>>();
    container.AddScoped<IUnitOfWork<BloggingContext>, UnitOfWork<BloggingContext>>();
}
1

1 Answers

2
votes

You should be able to register it in .NET Core's IOC like any .NET Core App:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<BloggingContext>(options => 
       options.UseSqlite("Data Source=blog.db"));
}

Then reference like a normal dependency in your ServiceStack Services:

public class MyServices : Service
{
     public BloggingContext BloggingContext { get; set; }
}

Which uses ServiceStack's .NET Core Container Adapter to resolve any dependencies not in ServiceStack's IOC, in .NET Core's IOC.