简体   繁体   中英

Sharing a transaction between EF DbContext in separated projects

What I want to do is to write some integration tests for a micro service environment using Entity Framework in which I need to isolate every tests scenario's transaction so that tests would not affect each other in terms of manipulating a database.

Then, after the tests are ran I want to check the database to see if the work is done correctly.

What I did so far is to create two base classes as followed. (I am using xunit as the testing framework)

public class DatabaseInitializer : IDisposable
{
    public readonly MyContext DatabaseContext;

    public DatabaseInitializer()
    {
        var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();

        var configuration = builder.Build();
        DatabaseContext = new MyContext(configuration.GetConnectionString("SqlConnectionString"));
        DatabaseContext.Database.EnsureCreated();
    }

    public void Dispose()
    {
        DatabaseContext.Database.EnsureDeleted();
    }
}

public class TransactionIsolator : IClassFixture<DatabaseInitializer> , IDisposable
{
    protected readonly TransactionScope TransactionScope;
    protected readonly MyContext DatabaseContext;

    public TransactionIsolator(DatabaseInitializer dbInitializer)
    {
        DatabaseContext = dbInitializer.DatabaseContext;
        TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
    }

    public void Dispose()
    {
        TransactionScope.Dispose();
    }
}

Here, the first ( DatabaseInitializer ) will ensure that the needed database is created at the first of test suite running and then deletes it upon its disposal.

The second class, will then create a transaction and rolls back the changes when the test is done. So when I write two tests like the followings it will correctly isolate my access to database for each test case running.

public class ExampleTests : TransactionIsolator
{
    public ExampleTests(DatabaseInitializer dbInitializer) : base(dbInitializer)
    {
    }

    [Fact]
    void example_test()
    {
        DatabaseContext.Indexes.Should().BeEmpty();
        DatabaseContext.Indexes.Add(new Indexes {PublisherId = "123", LastIndex = new byte[] {0x12, 0x13}});
        DatabaseContext.SaveChanges();
        DatabaseContext.Indexes.Should().NotBeEmpty();
    }

    [Fact]
    void example_test_2()
    {
        DatabaseContext.Indexes.Should().BeEmpty();
        DatabaseContext.Indexes.Add(new Indexes {PublisherId = "321", LastIndex = new byte[] {0x12, 0x13}});
        DatabaseContext.SaveChanges();
        DatabaseContext.Indexes.Should().NotBeEmpty();
    }
}

These will be passed and all is good EXCEPT I do not have any access to micro services to set their transaction or at least I don't have any idea whether if it is even possible or not. I mean this would be very awesome if I could configure or signal a micro service to use a transaction for its DB from outside of the scope of that application.

That aside, if it is not possible, what can you suggest to use in this situation I want to gain isolation for each test scenario and still be able to run them is parallel.

For most providers this "just works". TransactionScope sets the static property Transaction.Current , and ADO.Net Providers can (and typically do) Enlist in the ambient transaction. See: Implementing an Implicit Transaction using Transaction Scope

And at least for SQL Server, if your tests use the same connection string, and don't run concurrently, they will also use the same SqlConnection and not require promotion to a Distributed Transaction. As the SQLClient Connection Pool is partitioned by Transaction, and a subsequent request to the pool for the same (ConnectionString,Transaction) will use the same SqlConnection.

Note also for SQL Server that there are some initialization operations that can't be performed inside a transaction. You can't create or alter a database in a transaction. But you can create tables and indexes inside a database in a transaction. Other platforms have different restrictions. Oracle, for instance, automatically commits DDL statements.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM