6
6
/// any release. You should only use it directly in your code with extreme caution and knowing that
7
7
/// doing so can result in application failures when updating to a new Entity Framework Core release.
8
8
/// </summary>
9
- public class NpgsqlHistoryRepository : HistoryRepository
9
+ public class NpgsqlHistoryRepository : HistoryRepository , IHistoryRepository
10
10
{
11
11
/// <summary>
12
12
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -19,60 +19,29 @@ public NpgsqlHistoryRepository(HistoryRepositoryDependencies dependencies)
19
19
{
20
20
}
21
21
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
-
42
22
/// <summary>
43
23
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
44
24
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
45
25
/// any release. You should only use it directly in your code with extreme caution and knowing that
46
26
/// doing so can result in application failures when updating to a new Entity Framework Core release.
47
27
/// </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 ;
59
29
60
30
/// <summary>
61
31
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
62
32
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
63
33
/// any release. You should only use it directly in your code with extreme caution and knowing that
64
34
/// doing so can result in application failures when updating to a new Entity Framework Core release.
65
35
/// </summary>
66
- public override IDisposable GetDatabaseLock ( )
36
+ public override IMigrationsDatabaseLock AcquireDatabaseLock ( )
67
37
{
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 ( ) ;
70
39
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 ( ) ) ;
74
43
75
- return new DummyDisposable ( ) ;
44
+ return new NpgsqlMigrationDatabaseLock ( this ) ;
76
45
}
77
46
78
47
/// <summary>
@@ -81,19 +50,24 @@ public override IDisposable GetDatabaseLock()
81
50
/// any release. You should only use it directly in your code with extreme caution and knowing that
82
51
/// doing so can result in application failures when updating to a new Entity Framework Core release.
83
52
/// </summary>
84
- public override Task < IAsyncDisposable > GetDatabaseLockAsync ( CancellationToken cancellationToken = default )
53
+ public override async Task < IMigrationsDatabaseLock > AcquireDatabaseLockAsync ( CancellationToken cancellationToken = default )
85
54
{
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 ) ;
93
59
94
- return Task . FromResult < IAsyncDisposable > ( new DummyDisposable ( ) ) ;
60
+ return new NpgsqlMigrationDatabaseLock ( this ) ;
95
61
}
96
62
63
+ private RelationalCommandParameterObject CreateRelationalCommandParameters ( )
64
+ => new (
65
+ Dependencies . Connection ,
66
+ null ,
67
+ null ,
68
+ Dependencies . CurrentContext . Context ,
69
+ Dependencies . CommandLogger , CommandSource . Migrations ) ;
70
+
97
71
/// <summary>
98
72
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
99
73
/// 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
127
101
protected override bool InterpretExistsResult ( object ? value )
128
102
=> ( bool ? ) value == true ;
129
103
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
+
130
146
/// <summary>
131
147
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
132
148
/// 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)
136
152
public override string GetCreateIfNotExistsScript ( )
137
153
{
138
154
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") ;
140
156
}
141
157
142
158
/// <summary>
@@ -178,8 +194,16 @@ public override string GetEndIfScript()
178
194
END $EF$;
179
195
""" ;
180
196
181
- private sealed class DummyDisposable : IDisposable , IAsyncDisposable
197
+ private sealed class NpgsqlMigrationDatabaseLock ( IHistoryRepository historyRepository ) : IMigrationsDatabaseLock
182
198
{
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
+
183
207
public void Dispose ( )
184
208
{
185
209
}
0 commit comments