entity-framework-corexunit

Share the time spent by EF Core migration across independent tests or between same test runs


In my .NET 8 app, migrating my contexts takes 10 seconds on my machine. And since I run hundreds of independent xUnit tests that each have their own databases, the complete suite of tests ends up being too slow, even with parallelism.

I wonder what EF Core does so I can cache it as it's a common step to all my tests.

Any ideas? Maybe a pool of databases that the tests could pick, clear and reuse in parallel?


Solution

  • I ended up having a finite stack of unique database names. I pop a new name when needed and at the end of each test I clear the database to then recycle the database name by pushing it back to the stack.

    public class WebApplicationFactoryHelper
    {
        private static readonly ConcurrentStack<Guid> Suffixes = new([
            Guid.Parse("7a00615f-c58a-4d4f-a0b7-081a881f58ac"),
            Guid.Parse("2ff693ef-e0ba-4430-bfc1-ac1a9288c99b"),
            Guid.Parse("23ec38d9-7b9c-46b5-9fcb-861f76dd500b"),
            Guid.Parse("7cd51fb0-3a8e-4828-bab1-9d87c2703f1a"),
            ...
        ]);
    
    public static async Task RecycleDb(Guid suffix)
    {
        var connectionString = $"Server=localhost;Database={suffix};Trusted_Connection=True;TrustServerCertificate=Yes;MultipleActiveResultSets=true";
        const string sql = """
                           
                           EXEC sp_MSForEachTable 
                               @command1 = '
                               SET QUOTED_IDENTIFIER ON;
                            IF ''?'' <> ''[dbo].[__EFMigrationsHistory]''
                               BEGIN 
                                   IF ''?'' = ''[dbo].[AspNetUsers]''
                                   BEGIN 
                                       DELETE FROM dbo.AspNetUsers WHERE UserName <> ''SpecificSeededUserName''
                                   END
                                   ELSE 
                                   BEGIN 
                                       DELETE FROM ? 
                                   END
                               END'
                           """;
        await using var connection = new SqlConnection(connectionString);
        await connection.ExecuteAsync(sql);
        Suffixes.Push(suffix);
    }
    
    public static Guid ConfigureDbSync(IServiceCollection serviceCollection)
    {
        var suffix = Suffixes.TryPop(out var result) ? result : throw new Exception("not enough dbs");
        var connectionString = $"Server=localhost;Database={suffix};Trusted_Connection=True;TrustServerCertificate=Yes;MultipleActiveResultSets=true";
        serviceCollection.Replace(new ServiceDescriptor(typeof(SqlConnection), _ => new SqlConnection(connectionString), ServiceLifetime.Scoped));
        return suffix;
    }
    

    }