Skip to content

Commit ab708e4

Browse files
committed
Sync to EF Core 9.0.0-rc.2.24460.3
Closes npgsql#3223
1 parent b818e82 commit ab708e4

12 files changed

+161
-139
lines changed

Directory.Packages.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
3-
<EFCoreVersion>[9.0.0-rc.1.24451.1]</EFCoreVersion>
4-
<MicrosoftExtensionsVersion>9.0.0-rc.1.24431.7</MicrosoftExtensionsVersion>
3+
<EFCoreVersion>[9.0.0-rc.2.24460.3]</EFCoreVersion>
4+
<MicrosoftExtensionsVersion>9.0.0-rc.2.24456.9</MicrosoftExtensionsVersion>
55
<NpgsqlVersion>8.0.4</NpgsqlVersion>
66
</PropertyGroup>
77

src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs

+74-50
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
/// any release. You should only use it directly in your code with extreme caution and knowing that
77
/// doing so can result in application failures when updating to a new Entity Framework Core release.
88
/// </summary>
9-
public class NpgsqlHistoryRepository : HistoryRepository
9+
public class NpgsqlHistoryRepository : HistoryRepository, IHistoryRepository
1010
{
1111
/// <summary>
1212
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -19,60 +19,29 @@ public NpgsqlHistoryRepository(HistoryRepositoryDependencies dependencies)
1919
{
2020
}
2121

22-
// TODO: We override Exists() as a workaround for https://github.com/dotnet/efcore/issues/34569; this should be fixed on the EF side
23-
// before EF 9.0 is released
24-
25-
/// <summary>
26-
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
27-
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
28-
/// any release. You should only use it directly in your code with extreme caution and knowing that
29-
/// doing so can result in application failures when updating to a new Entity Framework Core release.
30-
/// </summary>
31-
public override bool Exists()
32-
=> Dependencies.DatabaseCreator.Exists()
33-
&& InterpretExistsResult(
34-
Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar(
35-
new RelationalCommandParameterObject(
36-
Dependencies.Connection,
37-
null,
38-
null,
39-
Dependencies.CurrentContext.Context,
40-
Dependencies.CommandLogger, CommandSource.Migrations)));
41-
4222
/// <summary>
4323
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
4424
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
4525
/// any release. You should only use it directly in your code with extreme caution and knowing that
4626
/// doing so can result in application failures when updating to a new Entity Framework Core release.
4727
/// </summary>
48-
public override async Task<bool> ExistsAsync(CancellationToken cancellationToken = default)
49-
=> await Dependencies.DatabaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false)
50-
&& InterpretExistsResult(
51-
await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync(
52-
new RelationalCommandParameterObject(
53-
Dependencies.Connection,
54-
null,
55-
null,
56-
Dependencies.CurrentContext.Context,
57-
Dependencies.CommandLogger, CommandSource.Migrations),
58-
cancellationToken).ConfigureAwait(false));
28+
public override LockReleaseBehavior LockReleaseBehavior => LockReleaseBehavior.Transaction;
5929

6030
/// <summary>
6131
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
6232
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
6333
/// any release. You should only use it directly in your code with extreme caution and knowing that
6434
/// doing so can result in application failures when updating to a new Entity Framework Core release.
6535
/// </summary>
66-
public override IDisposable GetDatabaseLock()
36+
public override IMigrationsDatabaseLock AcquireDatabaseLock()
6737
{
68-
// TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a
69-
// transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439.
38+
Dependencies.MigrationsLogger.AcquiringMigrationLock();
7039

71-
// Dependencies.RawSqlCommandBuilder
72-
// .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE")
73-
// .ExecuteNonQuery(CreateRelationalCommandParameters());
40+
Dependencies.RawSqlCommandBuilder
41+
.Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE")
42+
.ExecuteNonQuery(CreateRelationalCommandParameters());
7443

75-
return new DummyDisposable();
44+
return new NpgsqlMigrationDatabaseLock(this);
7645
}
7746

7847
/// <summary>
@@ -81,19 +50,24 @@ public override IDisposable GetDatabaseLock()
8150
/// any release. You should only use it directly in your code with extreme caution and knowing that
8251
/// doing so can result in application failures when updating to a new Entity Framework Core release.
8352
/// </summary>
84-
public override Task<IAsyncDisposable> GetDatabaseLockAsync(CancellationToken cancellationToken = default)
53+
public override async Task<IMigrationsDatabaseLock> AcquireDatabaseLockAsync(CancellationToken cancellationToken = default)
8554
{
86-
// TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a
87-
// transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439.
88-
89-
// await Dependencies.RawSqlCommandBuilder
90-
// .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE")
91-
// .ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken)
92-
// .ConfigureAwait(false);
55+
await Dependencies.RawSqlCommandBuilder
56+
.Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE")
57+
.ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken)
58+
.ConfigureAwait(false);
9359

94-
return Task.FromResult<IAsyncDisposable>(new DummyDisposable());
60+
return new NpgsqlMigrationDatabaseLock(this);
9561
}
9662

63+
private RelationalCommandParameterObject CreateRelationalCommandParameters()
64+
=> new(
65+
Dependencies.Connection,
66+
null,
67+
null,
68+
Dependencies.CurrentContext.Context,
69+
Dependencies.CommandLogger, CommandSource.Migrations);
70+
9771
/// <summary>
9872
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
9973
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -127,6 +101,48 @@ SELECT 1 FROM pg_catalog.pg_class c
127101
protected override bool InterpretExistsResult(object? value)
128102
=> (bool?)value == true;
129103

104+
bool IHistoryRepository.CreateIfNotExists()
105+
{
106+
// In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation
107+
// (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't
108+
// created.
109+
try
110+
{
111+
return Dependencies.MigrationCommandExecutor.ExecuteNonQuery(
112+
GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true)
113+
!= 0;
114+
}
115+
catch (PostgresException e) when (e.SqlState == "23505")
116+
{
117+
return false;
118+
}
119+
}
120+
121+
async Task<bool> IHistoryRepository.CreateIfNotExistsAsync(CancellationToken cancellationToken)
122+
{
123+
// In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation
124+
// (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't
125+
// created.
126+
try
127+
{
128+
return (await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(
129+
GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true,
130+
cancellationToken: cancellationToken).ConfigureAwait(false))
131+
!= 0;
132+
}
133+
catch (PostgresException e) when (e.SqlState == "23505")
134+
{
135+
return false;
136+
}
137+
}
138+
139+
private IReadOnlyList<MigrationCommand> GetCreateIfNotExistsCommands()
140+
=> Dependencies.MigrationsSqlGenerator.Generate([new SqlOperation
141+
{
142+
Sql = GetCreateIfNotExistsScript(),
143+
SuppressTransaction = true
144+
}]);
145+
130146
/// <summary>
131147
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
132148
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -136,7 +152,7 @@ protected override bool InterpretExistsResult(object? value)
136152
public override string GetCreateIfNotExistsScript()
137153
{
138154
var script = GetCreateScript();
139-
return script.Insert(script.IndexOf("CREATE TABLE", StringComparison.Ordinal) + 12, " IF NOT EXISTS");
155+
return script.Replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
140156
}
141157

142158
/// <summary>
@@ -178,8 +194,16 @@ public override string GetEndIfScript()
178194
END $EF$;
179195
""";
180196

181-
private sealed class DummyDisposable : IDisposable, IAsyncDisposable
197+
private sealed class NpgsqlMigrationDatabaseLock(IHistoryRepository historyRepository) : IMigrationsDatabaseLock
182198
{
199+
/// <summary>
200+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
201+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
202+
/// any release. You should only use it directly in your code with extreme caution and knowing that
203+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
204+
/// </summary>
205+
public IHistoryRepository HistoryRepository => historyRepository;
206+
183207
public void Dispose()
184208
{
185209
}

src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ public NpgsqlMigrator(
3939
IDatabaseProvider databaseProvider,
4040
IMigrationsModelDiffer migrationsModelDiffer,
4141
IDesignTimeModel designTimeModel,
42-
IDbContextOptions contextOptions)
42+
IDbContextOptions contextOptions,
43+
IExecutionStrategy executionStrategy)
4344
: base(migrationsAssembly, historyRepository, databaseCreator, migrationsSqlGenerator, rawSqlCommandBuilder,
4445
migrationCommandExecutor, connection, sqlGenerationHelper, currentContext, modelRuntimeInitializer, logger,
45-
commandLogger, databaseProvider, migrationsModelDiffer, designTimeModel, contextOptions)
46+
commandLogger, databaseProvider, migrationsModelDiffer, designTimeModel, contextOptions, executionStrategy)
4647
{
4748
_historyRepository = historyRepository;
4849
_connection = connection;

test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,6 @@ public void Can_use_computed_columns_with_nullable_enum()
139139
public async Task InitializeAsync()
140140
=> TestStore = await NpgsqlTestStore.CreateInitializedAsync("ComputedColumnTest");
141141

142-
public Task DisposeAsync()
143-
{
144-
TestStore.Dispose();
145-
return Task.CompletedTask;
146-
}
142+
public async Task DisposeAsync()
143+
=> await TestStore.DisposeAsync();
147144
}

test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs

-19
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations
77
public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture)
88
: MigrationsInfrastructureTestBase<MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture>(fixture)
99
{
10-
// TODO: The following test the migration lock, which isn't yet implemented - waiting for EF-side fixes in rc.2
11-
#region Unskip for 9.0.0-rc.2
12-
13-
public override void Can_apply_one_migration_in_parallel()
14-
{
15-
}
16-
17-
public override Task Can_apply_one_migration_in_parallel_async()
18-
=> Task.CompletedTask;
19-
20-
public override void Can_apply_second_migration_in_parallel()
21-
{
22-
}
23-
24-
public override Task Can_apply_second_migration_in_parallel_async()
25-
=> Task.CompletedTask;
26-
27-
#endregion Unskip for 9.0.0-rc.2
28-
2910
public override void Can_get_active_provider()
3011
{
3112
base.Can_get_active_provider();

test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs

+13-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class NpgsqlValueGenerationScenariosTest
1010
[Fact]
1111
public async Task Insert_with_sequence_id()
1212
{
13-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
13+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
1414

1515
using (var context = new BlogContextSequence(testStore.Name))
1616
{
@@ -35,7 +35,7 @@ public class BlogContextSequence(string databaseName) : ContextBase(databaseName
3535
[Fact]
3636
public async Task Insert_with_sequence_HiLo()
3737
{
38-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
38+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
3939

4040
using (var context = new BlogContextHiLo(testStore.Name))
4141
{
@@ -68,7 +68,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
6868
[Fact]
6969
public async Task Insert_with_default_value_from_sequence()
7070
{
71-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
71+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
7272

7373
using (var context = new BlogContextDefaultValue(testStore.Name))
7474
{
@@ -146,7 +146,7 @@ public class BlogWithStringKey
146146
[Fact]
147147
public async Task Insert_with_key_default_value_from_sequence()
148148
{
149-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
149+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
150150

151151
using (var context = new BlogContextKeyColumnWithDefaultValue(testStore.Name))
152152
{
@@ -187,7 +187,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
187187
[ConditionalFact]
188188
public async Task Insert_uint_to_Identity_column_using_value_converter()
189189
{
190-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
190+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
191191
using (var context = new BlogContextUIntToIdentityUsingValueConverter(testStore.Name))
192192
{
193193
context.Database.EnsureCreatedResiliently();
@@ -231,7 +231,7 @@ public class BlogWithUIntKey
231231
[ConditionalFact]
232232
public async Task Insert_string_to_Identity_column_using_value_converter()
233233
{
234-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
234+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
235235
using (var context = new BlogContextStringToIdentityUsingValueConverter(testStore.Name))
236236
{
237237
context.Database.EnsureCreatedResiliently();
@@ -276,7 +276,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
276276
[Fact]
277277
public async Task Insert_with_explicit_non_default_keys()
278278
{
279-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
279+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
280280

281281
using (var context = new BlogContextNoKeyGeneration(testStore.Name))
282282
{
@@ -312,7 +312,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
312312
[Fact]
313313
public async Task Insert_with_explicit_with_default_keys()
314314
{
315-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
315+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
316316

317317
using (var context = new BlogContextNoKeyGenerationNullableKey(testStore.Name))
318318
{
@@ -348,7 +348,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
348348
[Fact]
349349
public async Task Insert_with_non_key_default_value()
350350
{
351-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
351+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
352352

353353
using (var context = new BlogContextNonKeyDefaultValue(testStore.Name))
354354
{
@@ -401,7 +401,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
401401
[Fact]
402402
public async Task Insert_with_non_key_default_value_readonly()
403403
{
404-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
404+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
405405

406406
using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name))
407407
{
@@ -455,7 +455,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
455455
[Fact]
456456
public async Task Insert_with_serial_non_id()
457457
{
458-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
458+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
459459

460460
int afterSave;
461461

@@ -493,7 +493,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
493493
[Fact]
494494
public async Task Insert_with_client_generated_GUID_key()
495495
{
496-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
496+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
497497

498498
Guid afterSave;
499499
using (var context = new BlogContext(testStore.Name))
@@ -518,7 +518,7 @@ public class BlogContext(string databaseName) : ContextBase(databaseName);
518518
[Fact]
519519
public async Task Insert_with_server_generated_GUID_key()
520520
{
521-
using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
521+
await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName);
522522

523523
Guid afterSave;
524524
using (var context = new BlogContextServerGuidKey(testStore.Name))

test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ public virtual void Dispose()
8484
{
8585
}
8686

87-
public virtual Task DisposeAsync()
88-
=> _testStore.DisposeAsync();
87+
public virtual async Task DisposeAsync()
88+
=> await _testStore.DisposeAsync();
8989
}
9090

9191
public class CompatibilityTestEntity

0 commit comments

Comments
 (0)