-
Notifications
You must be signed in to change notification settings - Fork 227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync to EF Core 9.0.0-rc.2.24460.3 #3275
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public class NpgsqlHistoryRepository : HistoryRepository | ||
public class NpgsqlHistoryRepository : HistoryRepository, IHistoryRepository | ||
{ | ||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
|
@@ -19,60 +19,29 @@ public NpgsqlHistoryRepository(HistoryRepositoryDependencies dependencies) | |
{ | ||
} | ||
|
||
// TODO: We override Exists() as a workaround for https://github.com/dotnet/efcore/issues/34569; this should be fixed on the EF side | ||
// before EF 9.0 is released | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public override bool Exists() | ||
=> Dependencies.DatabaseCreator.Exists() | ||
&& InterpretExistsResult( | ||
Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar( | ||
new RelationalCommandParameterObject( | ||
Dependencies.Connection, | ||
null, | ||
null, | ||
Dependencies.CurrentContext.Context, | ||
Dependencies.CommandLogger, CommandSource.Migrations))); | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public override async Task<bool> ExistsAsync(CancellationToken cancellationToken = default) | ||
=> await Dependencies.DatabaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false) | ||
&& InterpretExistsResult( | ||
await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( | ||
new RelationalCommandParameterObject( | ||
Dependencies.Connection, | ||
null, | ||
null, | ||
Dependencies.CurrentContext.Context, | ||
Dependencies.CommandLogger, CommandSource.Migrations), | ||
cancellationToken).ConfigureAwait(false)); | ||
public override LockReleaseBehavior LockReleaseBehavior => LockReleaseBehavior.Transaction; | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public override IDisposable GetDatabaseLock() | ||
public override IMigrationsDatabaseLock AcquireDatabaseLock() | ||
{ | ||
// TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a | ||
// transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. | ||
Dependencies.MigrationsLogger.AcquiringMigrationLock(); | ||
|
||
// Dependencies.RawSqlCommandBuilder | ||
// .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") | ||
// .ExecuteNonQuery(CreateRelationalCommandParameters()); | ||
Dependencies.RawSqlCommandBuilder | ||
.Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") | ||
.ExecuteNonQuery(CreateRelationalCommandParameters()); | ||
|
||
return new DummyDisposable(); | ||
return new NpgsqlMigrationDatabaseLock(this); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -81,19 +50,24 @@ public override IDisposable GetDatabaseLock() | |
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public override Task<IAsyncDisposable> GetDatabaseLockAsync(CancellationToken cancellationToken = default) | ||
public override async Task<IMigrationsDatabaseLock> AcquireDatabaseLockAsync(CancellationToken cancellationToken = default) | ||
{ | ||
// TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a | ||
// transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. | ||
|
||
// await Dependencies.RawSqlCommandBuilder | ||
// .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") | ||
// .ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken) | ||
// .ConfigureAwait(false); | ||
await Dependencies.RawSqlCommandBuilder | ||
.Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") | ||
.ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
return Task.FromResult<IAsyncDisposable>(new DummyDisposable()); | ||
return new NpgsqlMigrationDatabaseLock(this); | ||
} | ||
|
||
private RelationalCommandParameterObject CreateRelationalCommandParameters() | ||
=> new( | ||
Dependencies.Connection, | ||
null, | ||
null, | ||
Dependencies.CurrentContext.Context, | ||
Dependencies.CommandLogger, CommandSource.Migrations); | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// 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 | |
protected override bool InterpretExistsResult(object? value) | ||
=> (bool?)value == true; | ||
|
||
bool IHistoryRepository.CreateIfNotExists() | ||
{ | ||
// In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation | ||
// (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't | ||
// created. | ||
try | ||
{ | ||
return Dependencies.MigrationCommandExecutor.ExecuteNonQuery( | ||
GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true) | ||
!= 0; | ||
} | ||
catch (PostgresException e) when (e.SqlState == "23505") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AndriySvyryd note this annoying thing to deal with the PG problem of doing CREATE TABLE IF NOT EXISTS in parallel :/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's only a real issue when running test code that exercises these convoluted scenarios. I'd say that the correct thing here is to let it throw and handle it in the execution strategy used in tests |
||
{ | ||
return false; | ||
} | ||
} | ||
|
||
async Task<bool> IHistoryRepository.CreateIfNotExistsAsync(CancellationToken cancellationToken) | ||
{ | ||
// In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation | ||
// (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't | ||
// created. | ||
try | ||
{ | ||
return (await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync( | ||
GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true, | ||
cancellationToken: cancellationToken).ConfigureAwait(false)) | ||
!= 0; | ||
} | ||
catch (PostgresException e) when (e.SqlState == "23505") | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
private IReadOnlyList<MigrationCommand> GetCreateIfNotExistsCommands() | ||
=> Dependencies.MigrationsSqlGenerator.Generate([new SqlOperation | ||
{ | ||
Sql = GetCreateIfNotExistsScript(), | ||
SuppressTransaction = true | ||
}]); | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// 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) | |
public override string GetCreateIfNotExistsScript() | ||
{ | ||
var script = GetCreateScript(); | ||
return script.Insert(script.IndexOf("CREATE TABLE", StringComparison.Ordinal) + 12, " IF NOT EXISTS"); | ||
return script.Replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing like some string-based code reuse 😆 |
||
} | ||
|
||
/// <summary> | ||
|
@@ -178,8 +194,16 @@ public override string GetEndIfScript() | |
END $EF$; | ||
"""; | ||
|
||
private sealed class DummyDisposable : IDisposable, IAsyncDisposable | ||
private sealed class NpgsqlMigrationDatabaseLock(IHistoryRepository historyRepository) : IMigrationsDatabaseLock | ||
{ | ||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public IHistoryRepository HistoryRepository => historyRepository; | ||
|
||
public void Dispose() | ||
{ | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AndriySvyryd here's the PG implementation of the final implementation of the migration lock... Everything seems OK (at least the MigrationsInfrastructureTestBase.Can_apply_*_in_parallel are passing).
Note that NpgsqlMigrationDatabaseLock is a no-op, as it generally will always be when LockReleaseBehavior is Transaction (since the lock is implicitly released when the transaction terminates, and not before). So it effectively functions only as a wrapper for MigrationHistory - feels a bit weird, but fine.