I will show you how to do lazy db context initialization with Entity Framework 7.
The idea is simple, we need an easy way to get database context in a request. If db context was used in the request we should call SaveChanges method and dispose used context if not we shouldn't do anything.
For "client", code should look like this:
public class SomeRepository
{
private readonly IDbContext _db;
public SomeRepository(IDbContext db)
{
_db = db;
}
public void Add(Item item)
{
_db.Current.Items.Add(item);
}
}
Nothing more. All routine will happen under the hood.
Let's define IDbContext interface first:
public interface IDbContext
{
void SetContext(Lazy<DbContext> context);
DbContext Current { get; }
}
And "lazy" implementation:
public class LazyDbContext : IDbContext
{
private Lazy<DbContext> _currentContext;
public void SetContext(Lazy<DbContext> currentContext)
{
_currentContext = currentContext;
}
public DbContext Current => _currentContext.Value;
}
As you can see this is just a "container" for our lazy context initializer. Whenever we want to use DbContext we should call IDbContext.Current and it will be created.
Let's register this implementation in DI container:
services.AddScoped<IDbContext, LazyDbContext>();
As you can see I've used scoped context. LazyDbContext will be created for each request.
Now we need a place to create lazy initializer and place to call SaveChanges and Dispose method in case DbContext was used. For this we can use SessionMiddleware:
public class SessionMiddleware
{
private readonly RequestDelegate _request;
public SessionMiddleware(RequestDelegate request)
{
_request = request;
}
public async Task Invoke(HttpContext context)
{
var contextProvider = context.RequestServices.GetService(typeof(IDbContext)) as IDbContext;
if (contextProvider == null)
{
throw new Exception("IDbContext is not registered.");
}
var lazyContext = new Lazy<DbContext>(() => new DbContext());
contextProvider.SetContext(lazyContext);
using (new RequestDbContext(lazyContext))
{
await _request(context);
}
}
}
I am using Disposable pattern here to ensure that DbContext is destroyed. Here is implementation:
public class RequestDbContext : IDisposable
{
private readonly Lazy<DbContext> _context;
public RequestDbContext(Lazy<DbContext> context)
{
_context = context;
}
public void Dispose()
{
if (_context.IsValueCreated && _context.Value != null)
{
_context.Value.SaveChanges();
_context.Value.Dispose();
}
}
}
And now register your SessionMiddleware:
app.UseMiddleware<SessionMiddleware>();
As you can see implementation is pretty simple but weel enough to meet my requirements. Now we will have access to the same DbContext in the same request. And if we do not use DbContext at all it will not be created.
All of this was tested on Linux with SQLite database provider.