22using System . Collections . Generic ;
33using System . Diagnostics . CodeAnalysis ;
44using System . Text . Json ;
5- using System . Threading ;
65using Npgsql . Internal ;
76using Npgsql . Internal . Postgres ;
87using Npgsql . Internal . ResolverFactories ;
@@ -14,84 +13,64 @@ sealed class GlobalTypeMapper : INpgsqlTypeMapper
1413{
1514 readonly UserTypeMapper _userTypeMapper = new ( ) ;
1615 readonly List < PgTypeInfoResolverFactory > _pluginResolverFactories = [ ] ;
17- readonly ReaderWriterLockSlim _lock = new ( ) ;
16+ readonly object _sync = new ( ) ;
1817 PgTypeInfoResolverFactory [ ] _typeMappingResolvers = [ ] ;
1918
2019 internal IEnumerable < PgTypeInfoResolverFactory > GetPluginResolverFactories ( )
2120 {
22- var resolvers = new List < PgTypeInfoResolverFactory > ( ) ;
23- _lock . EnterReadLock ( ) ;
24- try
25- {
26- resolvers . AddRange ( _pluginResolverFactories ) ;
27- }
28- finally
29- {
30- _lock . ExitReadLock ( ) ;
31- }
32-
33- return resolvers ;
21+ lock ( _sync )
22+ return new List < PgTypeInfoResolverFactory > ( _pluginResolverFactories ) ;
3423 }
3524
3625 internal PgTypeInfoResolverFactory ? GetUserMappingsResolverFactory ( )
3726 {
38- _lock . EnterReadLock ( ) ;
39- try
40- {
27+ lock ( _sync )
4128 return _userTypeMapper . Items . Count > 0 ? _userTypeMapper : null ;
42- }
43- finally
44- {
45- _lock . ExitReadLock ( ) ;
46- }
4729 }
4830
4931 internal void AddGlobalTypeMappingResolvers ( PgTypeInfoResolverFactory [ ] factories , Func < PgTypeInfoResolverChainBuilder > ? builderFactory = null , bool overwrite = false )
5032 {
51- // Good enough logic to prevent SlimBuilder overriding the normal Builder.
52- if ( overwrite || factories . Length > _typeMappingResolvers . Length )
33+ lock ( _sync )
5334 {
54- _builderFactory = builderFactory ;
55- _typeMappingResolvers = factories ;
56- ResetTypeMappingCache ( ) ;
35+ // Good enough logic to prevent SlimBuilder overriding the normal Builder.
36+ if ( overwrite || factories . Length > _typeMappingResolvers . Length )
37+ {
38+ _builderFactory = builderFactory ;
39+ _typeMappingResolvers = factories ;
40+ _typeMappingOptions = null ;
41+ }
5742 }
5843 }
5944
60- void ResetTypeMappingCache ( ) => _typeMappingOptions = null ;
61-
6245 PgSerializerOptions ? _typeMappingOptions ;
6346 Func < PgTypeInfoResolverChainBuilder > ? _builderFactory ;
6447 JsonSerializerOptions ? _jsonSerializerOptions ;
6548
66- PgSerializerOptions TypeMappingOptions
49+ PgSerializerOptions TypeMappingOptions => _typeMappingOptions ?? BuildTypeMappingOptions ( ) ;
50+
51+ PgSerializerOptions BuildTypeMappingOptions ( )
6752 {
68- get
53+ lock ( _sync )
6954 {
70- if ( _typeMappingOptions is not null )
71- return _typeMappingOptions ;
72-
73- _lock . EnterReadLock ( ) ;
74- try
55+ if ( _typeMappingOptions is { } existing )
56+ return existing ;
57+
58+ var builder = _builderFactory ? . Invoke ( ) ?? new ( ) ;
59+ builder . AppendResolverFactory ( _userTypeMapper ) ;
60+ foreach ( var factory in _pluginResolverFactories )
61+ builder . AppendResolverFactory ( factory ) ;
62+ foreach ( var factory in _typeMappingResolvers )
63+ builder . AppendResolverFactory ( factory ) ;
64+ var chain = builder . Build ( ) ;
65+ var options = new PgSerializerOptions ( PostgresMinimalDatabaseInfo . DefaultTypeCatalog , chain )
7566 {
76- var builder = _builderFactory ? . Invoke ( ) ?? new ( ) ;
77- builder . AppendResolverFactory ( _userTypeMapper ) ;
78- foreach ( var factory in _pluginResolverFactories )
79- builder . AppendResolverFactory ( factory ) ;
80- foreach ( var factory in _typeMappingResolvers )
81- builder . AppendResolverFactory ( factory ) ;
82- var chain = builder . Build ( ) ;
83- return _typeMappingOptions = new ( PostgresMinimalDatabaseInfo . DefaultTypeCatalog , chain )
84- {
85- // This means we don't ever have a missing oid for a datatypename as our canonical format is datatypenames.
86- PortableTypeIds = true ,
87- // Don't throw if our catalog doesn't know the datatypename.
88- IntrospectionMode = true
89- } ;
90- }
91- finally
92- {
93- _lock . ExitReadLock ( ) ;
94- }
67+ // This means we don't ever have a missing oid for a datatypename as our canonical format is datatypenames.
68+ PortableTypeIds = true ,
69+ // Don't throw if our catalog doesn't know the datatypename.
70+ IntrospectionMode = true
71+ } ;
72+ _typeMappingOptions = options ;
73+ return options ;
9574 }
9675 }
9776
@@ -127,8 +106,7 @@ static GlobalTypeMapper()
127106 /// <inheritdoc />
128107 public void AddTypeInfoResolverFactory ( PgTypeInfoResolverFactory factory )
129108 {
130- _lock . EnterWriteLock ( ) ;
131- try
109+ lock ( _sync )
132110 {
133111 var type = factory . GetType ( ) ;
134112
@@ -146,53 +124,21 @@ public void AddTypeInfoResolverFactory(PgTypeInfoResolverFactory factory)
146124 }
147125
148126 _pluginResolverFactories . Insert ( 0 , factory ) ;
149- ResetTypeMappingCache ( ) ;
150- }
151- finally
152- {
153- _lock . ExitWriteLock ( ) ;
127+ _typeMappingOptions = null ;
154128 }
155129 }
156130
157131 public void AddDbTypeResolverFactory ( DbTypeResolverFactory factory )
158132 => throw new NotSupportedException ( "The global type mapper does not support DbTypeResolverFactories. Call this method on a data source builder instead." ) ;
159133
160- void ReplaceTypeInfoResolverFactory ( PgTypeInfoResolverFactory factory )
161- {
162- _lock . EnterWriteLock ( ) ;
163- try
164- {
165- var type = factory . GetType ( ) ;
166-
167- for ( var i = 0 ; i < _pluginResolverFactories . Count ; i ++ )
168- {
169- if ( _pluginResolverFactories [ i ] . GetType ( ) == type )
170- {
171- _pluginResolverFactories [ i ] = factory ;
172- break ;
173- }
174- }
175-
176- ResetTypeMappingCache ( ) ;
177- }
178- finally
179- {
180- _lock . ExitWriteLock ( ) ;
181- }
182- }
183-
184134 /// <inheritdoc />
185135 public void Reset ( )
186136 {
187- _lock . EnterWriteLock ( ) ;
188- try
137+ lock ( _sync )
189138 {
190139 _pluginResolverFactories . Clear ( ) ;
191140 _userTypeMapper . Items . Clear ( ) ;
192- }
193- finally
194- {
195- _lock . ExitWriteLock ( ) ;
141+ _typeMappingOptions = null ;
196142 }
197143 }
198144
@@ -206,9 +152,25 @@ public INpgsqlNameTranslator DefaultNameTranslator
206152 /// <inheritdoc />
207153 public INpgsqlTypeMapper ConfigureJsonOptions ( JsonSerializerOptions serializerOptions )
208154 {
209- _jsonSerializerOptions = serializerOptions ;
210- // If JsonTypeInfoResolverFactory exists we replace it with a configured instance on the same index of the array.
211- ReplaceTypeInfoResolverFactory ( new JsonTypeInfoResolverFactory ( serializerOptions ) ) ;
155+ lock ( _sync )
156+ {
157+ _jsonSerializerOptions = serializerOptions ;
158+
159+ // If JsonTypeInfoResolverFactory exists we replace it with a configured instance on the same index of the array.
160+ var factory = new JsonTypeInfoResolverFactory ( serializerOptions ) ;
161+ var type = factory . GetType ( ) ;
162+
163+ for ( var i = 0 ; i < _pluginResolverFactories . Count ; i ++ )
164+ {
165+ if ( _pluginResolverFactories [ i ] . GetType ( ) == type )
166+ {
167+ _pluginResolverFactories [ i ] = factory ;
168+ break ;
169+ }
170+ }
171+
172+ _typeMappingOptions = null ;
173+ }
212174 return this ;
213175 }
214176
@@ -219,7 +181,9 @@ public INpgsqlTypeMapper EnableDynamicJson(
219181 Type [ ] ? jsonbClrTypes = null ,
220182 Type [ ] ? jsonClrTypes = null )
221183 {
222- AddTypeInfoResolverFactory ( new JsonDynamicTypeInfoResolverFactory ( jsonbClrTypes , jsonClrTypes , _jsonSerializerOptions ) ) ;
184+ // Use a re-entered lock to add the read of _jsonSerializerOptions to the total scope.
185+ lock ( _sync )
186+ AddTypeInfoResolverFactory ( new JsonDynamicTypeInfoResolverFactory ( jsonbClrTypes , jsonClrTypes , _jsonSerializerOptions ) ) ;
223187 return this ;
224188 }
225189
@@ -244,68 +208,48 @@ public INpgsqlTypeMapper EnableUnmappedTypes()
244208 /// <inheritdoc />
245209 public INpgsqlTypeMapper MapEnum < [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields ) ] TEnum > ( string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null ) where TEnum : struct , Enum
246210 {
247- _lock . EnterWriteLock ( ) ;
248- try
211+ lock ( _sync )
249212 {
250213 _userTypeMapper . MapEnum < TEnum > ( pgName , nameTranslator ) ;
251- ResetTypeMappingCache ( ) ;
214+ _typeMappingOptions = null ;
252215 return this ;
253216 }
254- finally
255- {
256- _lock . ExitWriteLock ( ) ;
257- }
258217 }
259218
260219 /// <inheritdoc />
261220 public bool UnmapEnum < [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields ) ] TEnum > ( string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null ) where TEnum : struct , Enum
262221 {
263- _lock . EnterWriteLock ( ) ;
264- try
222+ lock ( _sync )
265223 {
266224 var removed = _userTypeMapper . UnmapEnum < TEnum > ( pgName , nameTranslator ) ;
267- ResetTypeMappingCache ( ) ;
225+ _typeMappingOptions = null ;
268226 return removed ;
269227 }
270- finally
271- {
272- _lock . ExitWriteLock ( ) ;
273- }
274228 }
275229
276230 /// <inheritdoc />
277231 [ RequiresDynamicCode ( "Calling MapEnum with a Type can require creating new generic types or methods. This may not work when AOT compiling." ) ]
278232 public INpgsqlTypeMapper MapEnum ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields | DynamicallyAccessedMemberTypes . PublicParameterlessConstructor ) ]
279233 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
280234 {
281- _lock . EnterWriteLock ( ) ;
282- try
235+ lock ( _sync )
283236 {
284237 _userTypeMapper . MapEnum ( clrType , pgName , nameTranslator ) ;
285- ResetTypeMappingCache ( ) ;
238+ _typeMappingOptions = null ;
286239 return this ;
287240 }
288- finally
289- {
290- _lock . ExitWriteLock ( ) ;
291- }
292241 }
293242
294243 /// <inheritdoc />
295244 public bool UnmapEnum ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields | DynamicallyAccessedMemberTypes . PublicParameterlessConstructor ) ]
296245 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
297246 {
298- _lock . EnterWriteLock ( ) ;
299- try
247+ lock ( _sync )
300248 {
301249 var removed = _userTypeMapper . UnmapEnum ( clrType , pgName , nameTranslator ) ;
302- ResetTypeMappingCache ( ) ;
250+ _typeMappingOptions = null ;
303251 return removed ;
304252 }
305- finally
306- {
307- _lock . ExitWriteLock ( ) ;
308- }
309253 }
310254
311255 /// <inheritdoc />
@@ -323,34 +267,24 @@ public bool UnmapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes
323267 public INpgsqlTypeMapper MapComposite ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicConstructors | DynamicallyAccessedMemberTypes . PublicProperties | DynamicallyAccessedMemberTypes . PublicFields ) ]
324268 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
325269 {
326- _lock . EnterWriteLock ( ) ;
327- try
270+ lock ( _sync )
328271 {
329272 _userTypeMapper . MapComposite ( clrType , pgName , nameTranslator ) ;
330- ResetTypeMappingCache ( ) ;
273+ _typeMappingOptions = null ;
331274 return this ;
332275 }
333- finally
334- {
335- _lock . ExitWriteLock ( ) ;
336- }
337276 }
338277
339278 /// <inheritdoc />
340279 [ RequiresDynamicCode ( "Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you." ) ]
341280 public bool UnmapComposite ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicConstructors | DynamicallyAccessedMemberTypes . PublicProperties | DynamicallyAccessedMemberTypes . PublicFields ) ]
342281 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
343282 {
344- _lock . EnterWriteLock ( ) ;
345- try
283+ lock ( _sync )
346284 {
347285 var result = _userTypeMapper . UnmapComposite ( clrType , pgName , nameTranslator ) ;
348- ResetTypeMappingCache ( ) ;
286+ _typeMappingOptions = null ;
349287 return result ;
350288 }
351- finally
352- {
353- _lock . ExitWriteLock ( ) ;
354- }
355289 }
356290}
0 commit comments