1
+ using System . Collections . Concurrent ;
1
2
using System . Data . Common ;
2
3
using Npgsql . EntityFrameworkCore . PostgreSQL . Infrastructure ;
3
4
using Npgsql . EntityFrameworkCore . PostgreSQL . Infrastructure . Internal ;
4
- using Npgsql . EntityFrameworkCore . PostgreSQL . Internal ;
5
5
6
6
namespace Npgsql . EntityFrameworkCore . PostgreSQL . Storage . Internal ;
7
7
@@ -27,11 +27,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
27
27
/// </remarks>
28
28
public class NpgsqlDataSourceManager : IDisposable , IAsyncDisposable
29
29
{
30
- private bool _isInitialized ;
31
- private string ? _connectionString ;
32
30
private readonly IEnumerable < INpgsqlDataSourceConfigurationPlugin > _plugins ;
33
- private NpgsqlDataSource ? _dataSource ;
34
- private readonly object _lock = new ( ) ;
31
+ private readonly ConcurrentDictionary < string , NpgsqlDataSource > _dataSources = new ( ) ;
32
+ private volatile int _isDisposed ;
35
33
36
34
/// <summary>
37
35
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -70,39 +68,39 @@ public NpgsqlDataSourceManager(IEnumerable<INpgsqlDataSourceConfigurationPlugin>
70
68
{ ConnectionString : null } or null => null ,
71
69
72
70
// The following are features which require an NpgsqlDataSource, since they require configuration on NpgsqlDataSourceBuilder.
73
- { EnumDefinitions . Count : > 0 } => GetSingletonDataSource ( npgsqlOptionsExtension , "MapEnum" ) ,
74
- _ when _plugins . Any ( ) => GetSingletonDataSource ( npgsqlOptionsExtension , _plugins . First ( ) . GetType ( ) . Name ) ,
71
+ { EnumDefinitions . Count : > 0 } => GetSingletonDataSource ( npgsqlOptionsExtension ) ,
72
+ _ when _plugins . Any ( ) => GetSingletonDataSource ( npgsqlOptionsExtension ) ,
75
73
76
74
// If there's no configured feature which requires us to use a data source internally, don't use one; this causes
77
75
// NpgsqlRelationalConnection to use the connection string as before (no data source), allowing switching connection strings
78
76
// with the same service provider etc.
79
77
_ => null
80
78
} ;
81
79
82
- private DbDataSource GetSingletonDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension , string dataSourceFeature )
80
+ private DbDataSource GetSingletonDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension )
83
81
{
84
- if ( ! _isInitialized )
82
+ var connectionString = npgsqlOptionsExtension . ConnectionString ;
83
+ Check . DebugAssert ( connectionString is not null , "Connection string can't be null" ) ;
84
+
85
+ if ( _dataSources . TryGetValue ( connectionString , out var dataSource ) )
85
86
{
86
- lock ( _lock )
87
- {
88
- if ( ! _isInitialized )
89
- {
90
- _dataSource = CreateSingletonDataSource ( npgsqlOptionsExtension ) ;
91
- _connectionString = npgsqlOptionsExtension . ConnectionString ;
92
- _isInitialized = true ;
93
- return _dataSource ;
94
- }
95
- }
87
+ return dataSource ;
96
88
}
97
89
98
- Check . DebugAssert ( _dataSource is not null , "_dataSource cannot be null at this point" ) ;
90
+ var newDataSource = CreateDataSource ( npgsqlOptionsExtension ) ;
99
91
100
- if ( _connectionString != npgsqlOptionsExtension . ConnectionString )
92
+ var addedDataSource = _dataSources . GetOrAdd ( connectionString , newDataSource ) ;
93
+ if ( ! ReferenceEquals ( addedDataSource , newDataSource ) )
101
94
{
102
- throw new InvalidOperationException ( NpgsqlStrings . DataSourceWithMultipleConnectionStrings ( dataSourceFeature ) ) ;
95
+ newDataSource . Dispose ( ) ;
96
+ }
97
+ else if ( _isDisposed == 1 )
98
+ {
99
+ newDataSource . Dispose ( ) ;
100
+ throw new ObjectDisposedException ( nameof ( NpgsqlDataSourceManager ) ) ;
103
101
}
104
102
105
- return _dataSource ;
103
+ return addedDataSource ;
106
104
}
107
105
108
106
/// <summary>
@@ -111,7 +109,7 @@ private DbDataSource GetSingletonDataSource(NpgsqlOptionsExtension npgsqlOptions
111
109
/// any release. You should only use it directly in your code with extreme caution and knowing that
112
110
/// doing so can result in application failures when updating to a new Entity Framework Core release.
113
111
/// </summary>
114
- protected virtual NpgsqlDataSource CreateSingletonDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension )
112
+ protected virtual NpgsqlDataSource CreateDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension )
115
113
{
116
114
var dataSourceBuilder = new NpgsqlDataSourceBuilder ( npgsqlOptionsExtension . ConnectionString ) ;
117
115
@@ -151,7 +149,15 @@ enumDefinition.StoreTypeSchema is null
151
149
/// doing so can result in application failures when updating to a new Entity Framework Core release.
152
150
/// </summary>
153
151
public void Dispose ( )
154
- => _dataSource ? . Dispose ( ) ;
152
+ {
153
+ if ( Interlocked . CompareExchange ( ref _isDisposed , 1 , 0 ) == 0 )
154
+ {
155
+ foreach ( var dataSource in _dataSources . Values )
156
+ {
157
+ dataSource . Dispose ( ) ;
158
+ }
159
+ }
160
+ }
155
161
156
162
/// <summary>
157
163
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -161,9 +167,12 @@ public void Dispose()
161
167
/// </summary>
162
168
public async ValueTask DisposeAsync ( )
163
169
{
164
- if ( _dataSource != null )
170
+ if ( Interlocked . CompareExchange ( ref _isDisposed , 1 , 0 ) == 0 )
165
171
{
166
- await _dataSource . DisposeAsync ( ) . ConfigureAwait ( false ) ;
172
+ foreach ( var dataSource in _dataSources . Values )
173
+ {
174
+ await dataSource . DisposeAsync ( ) . ConfigureAwait ( false ) ;
175
+ }
167
176
}
168
177
}
169
178
}
0 commit comments