1+ using System . Collections . Concurrent ;
12using System . Data . Common ;
23using Npgsql . EntityFrameworkCore . PostgreSQL . Infrastructure ;
34using Npgsql . EntityFrameworkCore . PostgreSQL . Infrastructure . Internal ;
4- using Npgsql . EntityFrameworkCore . PostgreSQL . Internal ;
55
66namespace Npgsql . EntityFrameworkCore . PostgreSQL . Storage . Internal ;
77
@@ -27,11 +27,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
2727/// </remarks>
2828public class NpgsqlDataSourceManager : IDisposable , IAsyncDisposable
2929{
30- private bool _isInitialized ;
31- private string ? _connectionString ;
3230 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 ;
3533
3634 /// <summary>
3735 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -70,39 +68,39 @@ public NpgsqlDataSourceManager(IEnumerable<INpgsqlDataSourceConfigurationPlugin>
7068 { ConnectionString : null } or null => null ,
7169
7270 // 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 ) ,
7573
7674 // If there's no configured feature which requires us to use a data source internally, don't use one; this causes
7775 // NpgsqlRelationalConnection to use the connection string as before (no data source), allowing switching connection strings
7876 // with the same service provider etc.
7977 _ => null
8078 } ;
8179
82- private DbDataSource GetSingletonDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension , string dataSourceFeature )
80+ private DbDataSource GetSingletonDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension )
8381 {
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 ) )
8586 {
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 ;
9688 }
9789
98- Check . DebugAssert ( _dataSource is not null , "_dataSource cannot be null at this point" ) ;
90+ var newDataSource = CreateDataSource ( npgsqlOptionsExtension ) ;
9991
100- if ( _connectionString != npgsqlOptionsExtension . ConnectionString )
92+ var addedDataSource = _dataSources . GetOrAdd ( connectionString , newDataSource ) ;
93+ if ( ! ReferenceEquals ( addedDataSource , newDataSource ) )
10194 {
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 ) ) ;
103101 }
104102
105- return _dataSource ;
103+ return addedDataSource ;
106104 }
107105
108106 /// <summary>
@@ -111,7 +109,7 @@ private DbDataSource GetSingletonDataSource(NpgsqlOptionsExtension npgsqlOptions
111109 /// any release. You should only use it directly in your code with extreme caution and knowing that
112110 /// doing so can result in application failures when updating to a new Entity Framework Core release.
113111 /// </summary>
114- protected virtual NpgsqlDataSource CreateSingletonDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension )
112+ protected virtual NpgsqlDataSource CreateDataSource ( NpgsqlOptionsExtension npgsqlOptionsExtension )
115113 {
116114 var dataSourceBuilder = new NpgsqlDataSourceBuilder ( npgsqlOptionsExtension . ConnectionString ) ;
117115
@@ -151,7 +149,15 @@ enumDefinition.StoreTypeSchema is null
151149 /// doing so can result in application failures when updating to a new Entity Framework Core release.
152150 /// </summary>
153151 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+ }
155161
156162 /// <summary>
157163 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -161,9 +167,12 @@ public void Dispose()
161167 /// </summary>
162168 public async ValueTask DisposeAsync ( )
163169 {
164- if ( _dataSource != null )
170+ if ( Interlocked . CompareExchange ( ref _isDisposed , 1 , 0 ) == 0 )
165171 {
166- await _dataSource . DisposeAsync ( ) . ConfigureAwait ( false ) ;
172+ foreach ( var dataSource in _dataSources . Values )
173+ {
174+ await dataSource . DisposeAsync ( ) . ConfigureAwait ( false ) ;
175+ }
167176 }
168177 }
169178}
0 commit comments