dotnet / EntityFramework.Docs

Documentation for Entity Framework Core and Entity Framework 6
https://docs.microsoft.com/ef/
Creative Commons Attribution 4.0 International
1.63k stars 1.96k forks source link

Change the "Cross-context transaction (relational databases only)" #471

Open scottaddie opened 7 years ago

scottaddie commented 7 years ago

Moved from aspnet/Docs#4220. Filed by @Ciantic, pertaining to the following doc: https://docs.microsoft.com/ef/core/saving/transactions

I propose to change the section "Cross-context transaction (relational databases only)".

Current approach as it now stands suggest to change the super class constructor signature of DbContext, this can be cumbersome since everyone who develops DbContext super classes need to change their constructors.

With my approach below it is not required to refactor your precious DbContexts which may be separated in different projects etc. The super classes of DbContext can be "regular" and have a DbContextOptions as a argument as in every other example. The trick is then to provide the connection to shared contexts using the regular AddDbContext configuration.

  1. Create a simple DI class that wraps the shared connection e.g.
/// <summary>
/// This allows to share a DbConnection within a scope, e.g. between DbContexts so they can perform transactions 
/// </summary>
public class DbContextConnectionConfiguration : IDisposable
{
    private readonly DbConnection _dbConnection;
    private readonly Action<DbConnection, DbContextOptionsBuilder> _configuration;
    private readonly bool _dispose;

    public DbContextConnectionConfiguration(DbConnection dbConnection, Action<DbConnection, DbContextOptionsBuilder> configuration, bool dispose = true)
    {
        _dbConnection = dbConnection;
        _configuration = configuration;
        _dispose = dispose;
    }

    public void Configure(DbContextOptionsBuilder optionsBuilder)
    {
        _configuration(_dbConnection, optionsBuilder);
    }

    public void Dispose()
    {
        if (_dispose) { 
            _dbConnection.Dispose();
        }
    }
}

Now only thing required is to inject this to AddDbContext calls in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(t => new DbContextConnectionConfiguration(
            new SqliteConnection(Configuration.GetConnectionString("Default")),
            (c, o) => o.UseSqlite(c)
    ));
    services.AddDbContext<YourFirstDbContext>((s, o) =>
    {
        s.GetService<DbContextConnectionConfiguration>().Configure(o);
    });
    services.AddDbContext<YourSecondDbContext>((s, o) =>
    {
        s.GetService<DbContextConnectionConfiguration>().Configure(o);
    });
}

That's it, there is no need to refactor your DbContext's just to share a connection.

ajcvickers commented 7 years ago

@ajcvickers to investigate