npgsql / efcore.pg

Entity Framework Core provider for PostgreSQL
PostgreSQL License
1.54k stars 226 forks source link

Migrations behavior change in 9.0.0 #3326

Open MaceWindu opened 3 hours ago

MaceWindu commented 3 hours ago

We have a set of tests that use different contexts with following setup:

This works fine for different db providers (we test sqlite, sqlserver, mysql and npgsql) and versions (3.1, 6, 8). But in v9 now it fails to create database for Npgsql on second call to snippet above. Basically only 1 context being initialized per test run, second initialization fails with:

System.InvalidOperationException
  HResult=0x80131509
  Message=An exception has been raised that is likely due to a transient failure.
  Source=Npgsql.EntityFrameworkCore.PostgreSQL
  StackTrace:
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded) in Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal\NpgsqlExecutionStrategy.cs:line 28
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IReadOnlyList`1 migrationCommands, IRelationalConnection connection, MigrationExecutionState executionState, Boolean commitTransaction, Nullable`1 isolationLevel)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlDatabaseCreator.CreateTables() in Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal\NpgsqlDatabaseCreator.cs:line 210
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseCreator.EnsureCreated()
   at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureCreated()
   at LinqToDB.EntityFrameworkCore.Tests.ContextTestBase`1.InitializeDatabase(TContext context, String provider, String connectionString) in C:\GitHub\linq2db\Tests\EntityFrameworkCore\ContextTestBase.cs:line 119
   at LinqToDB.EntityFrameworkCore.Tests.ContextTestBase`1.CreateContext(String provider, Func`2 optionsSetter, Func`2 optionsBuilderSetter) in C:\GitHub\linq2db\Tests\EntityFrameworkCore\ContextTestBase.cs:line 159
   at LinqToDB.EntityFrameworkCore.Tests.InterceptorTests.TestEfCoreSideOfComboInterceptor(String provider) in C:\GitHub\linq2db\Tests\EntityFrameworkCore\Tests\InterceptorTests.cs:line 109
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)

  This exception was originally thrown at this call stack:
    System.Net.Sockets.NetworkStream.Read(byte[], int, int)

Inner Exception 1:
NpgsqlException: Exception while reading from stream

Inner Exception 2:
IOException: Unable to read data from the transport connection: An established connection was aborted by the software in your host machine..

Inner Exception 3:
SocketException: An established connection was aborted by the software in your host machine.

Currently looking into providing isolated repro code

MaceWindu commented 2 hours ago

Reproduced.

Note use of UseNodaTime option. Without it it works fine.

This is actually a workaround for another issue in v8 we hit and I didn't reported yet: only one of test contexts need nodatime integration, but starting from v8 release it UseNodaTime was ignored if not called on first UseNpgsql call in tests (3.1 and 6 versions respected this call for specific context)

using Microsoft.EntityFrameworkCore;

internal class Program
{
    private static void Main(string[] args)
    {
        var connectionString = "<connection_string_here>";

        for (var i = 0; i < 2; i++)
        {
            var optionsBuilder = new DbContextOptionsBuilder<TestContext>();
            optionsBuilder.UseNpgsql(connectionString, o => o.UseNodaTime());

            using (var ctx = new TestContext(optionsBuilder.Options))
            {
                ctx.Database.EnsureDeleted();
                ctx.Database.EnsureCreated();
            }
        }
    }
}

public class TestTable
{
    public int Id { get; set; }
}

public sealed class TestContext(DbContextOptions options) : DbContext(options)
{
    public DbSet<TestTable> Test { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TestTable>();
    }
}