33using System . Diagnostics . CodeAnalysis ;
44using System . Linq ;
55using System . Text . Json ;
6- using System . Text . Json . Nodes ;
7- using System . Threading ;
86using Npgsql . Internal ;
97using Npgsql . Internal . Postgres ;
108using Npgsql . Internal . ResolverFactories ;
@@ -16,87 +14,67 @@ namespace Npgsql.TypeMapping;
1614sealed class GlobalTypeMapper : INpgsqlTypeMapper
1715{
1816 readonly UserTypeMapper _userTypeMapper = new ( ) ;
19- readonly List < PgTypeInfoResolverFactory > _pluginResolverFactories = new ( ) ;
20- readonly ReaderWriterLockSlim _lock = new ( ) ;
21- PgTypeInfoResolverFactory [ ] _typeMappingResolvers = Array . Empty < PgTypeInfoResolverFactory > ( ) ;
17+ readonly List < PgTypeInfoResolverFactory > _pluginResolverFactories = [ ] ;
18+ readonly object _sync = new ( ) ;
19+ PgTypeInfoResolverFactory [ ] _typeMappingResolvers = [ ] ;
2220
2321 internal List < HackyEnumTypeMapping > HackyEnumTypeMappings { get ; } = new ( ) ;
2422
2523 internal IEnumerable < PgTypeInfoResolverFactory > GetPluginResolverFactories ( )
2624 {
27- var resolvers = new List < PgTypeInfoResolverFactory > ( ) ;
28- _lock . EnterReadLock ( ) ;
29- try
30- {
31- resolvers . AddRange ( _pluginResolverFactories ) ;
32- }
33- finally
34- {
35- _lock . ExitReadLock ( ) ;
36- }
37-
38- return resolvers ;
25+ lock ( _sync )
26+ return new List < PgTypeInfoResolverFactory > ( _pluginResolverFactories ) ;
3927 }
4028
4129 internal PgTypeInfoResolverFactory ? GetUserMappingsResolverFactory ( )
4230 {
43- _lock . EnterReadLock ( ) ;
44- try
45- {
31+ lock ( _sync )
4632 return _userTypeMapper . Items . Count > 0 ? _userTypeMapper : null ;
47- }
48- finally
49- {
50- _lock . ExitReadLock ( ) ;
51- }
5233 }
5334
5435 internal void AddGlobalTypeMappingResolvers ( PgTypeInfoResolverFactory [ ] factories , Func < PgTypeInfoResolverChainBuilder > ? builderFactory = null , bool overwrite = false )
5536 {
56- // Good enough logic to prevent SlimBuilder overriding the normal Builder.
57- if ( overwrite || factories . Length > _typeMappingResolvers . Length )
37+ lock ( _sync )
5838 {
59- _builderFactory = builderFactory ;
60- _typeMappingResolvers = factories ;
61- ResetTypeMappingCache ( ) ;
39+ // Good enough logic to prevent SlimBuilder overriding the normal Builder.
40+ if ( overwrite || factories . Length > _typeMappingResolvers . Length )
41+ {
42+ _builderFactory = builderFactory ;
43+ _typeMappingResolvers = factories ;
44+ _typeMappingOptions = null ;
45+ }
6246 }
6347 }
6448
65- void ResetTypeMappingCache ( ) => _typeMappingOptions = null ;
66-
6749 PgSerializerOptions ? _typeMappingOptions ;
6850 Func < PgTypeInfoResolverChainBuilder > ? _builderFactory ;
6951 JsonSerializerOptions ? _jsonSerializerOptions ;
7052
71- PgSerializerOptions TypeMappingOptions
53+ PgSerializerOptions TypeMappingOptions => _typeMappingOptions ?? BuildTypeMappingOptions ( ) ;
54+
55+ PgSerializerOptions BuildTypeMappingOptions ( )
7256 {
73- get
57+ lock ( _sync )
7458 {
75- if ( _typeMappingOptions is not null )
76- return _typeMappingOptions ;
77-
78- _lock . EnterReadLock ( ) ;
79- try
59+ if ( _typeMappingOptions is { } existing )
60+ return existing ;
61+
62+ var builder = _builderFactory ? . Invoke ( ) ?? new ( ) ;
63+ builder . AppendResolverFactory ( _userTypeMapper ) ;
64+ foreach ( var factory in _pluginResolverFactories )
65+ builder . AppendResolverFactory ( factory ) ;
66+ foreach ( var factory in _typeMappingResolvers )
67+ builder . AppendResolverFactory ( factory ) ;
68+ var chain = builder . Build ( ) ;
69+ var options = new PgSerializerOptions ( PostgresMinimalDatabaseInfo . DefaultTypeCatalog , chain )
8070 {
81- var builder = _builderFactory ? . Invoke ( ) ?? new ( ) ;
82- builder . AppendResolverFactory ( _userTypeMapper ) ;
83- foreach ( var factory in _pluginResolverFactories )
84- builder . AppendResolverFactory ( factory ) ;
85- foreach ( var factory in _typeMappingResolvers )
86- builder . AppendResolverFactory ( factory ) ;
87- var chain = builder . Build ( ) ;
88- return _typeMappingOptions = new ( PostgresMinimalDatabaseInfo . DefaultTypeCatalog , chain )
89- {
90- // This means we don't ever have a missing oid for a datatypename as our canonical format is datatypenames.
91- PortableTypeIds = true ,
92- // Don't throw if our catalog doesn't know the datatypename.
93- IntrospectionMode = true
94- } ;
95- }
96- finally
97- {
98- _lock . ExitReadLock ( ) ;
99- }
71+ // This means we don't ever have a missing oid for a datatypename as our canonical format is datatypenames.
72+ PortableTypeIds = true ,
73+ // Don't throw if our catalog doesn't know the datatypename.
74+ IntrospectionMode = true
75+ } ;
76+ _typeMappingOptions = options ;
77+ return options ;
10078 }
10179 }
10280
@@ -126,8 +104,7 @@ static GlobalTypeMapper()
126104 /// <inheritdoc />
127105 public void AddTypeInfoResolverFactory ( PgTypeInfoResolverFactory factory )
128106 {
129- _lock . EnterWriteLock ( ) ;
130- try
107+ lock ( _sync )
131108 {
132109 var type = factory . GetType ( ) ;
133110
@@ -145,51 +122,20 @@ public void AddTypeInfoResolverFactory(PgTypeInfoResolverFactory factory)
145122 }
146123
147124 _pluginResolverFactories . Insert ( 0 , factory ) ;
148- ResetTypeMappingCache ( ) ;
149- }
150- finally
151- {
152- _lock . ExitWriteLock ( ) ;
125+ _typeMappingOptions = null ;
153126 }
154127 }
155128
156- void ReplaceTypeInfoResolverFactory ( PgTypeInfoResolverFactory factory )
157- {
158- _lock . EnterWriteLock ( ) ;
159- try
160- {
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- ResetTypeMappingCache ( ) ;
173- }
174- finally
175- {
176- _lock . ExitWriteLock ( ) ;
177- }
178- }
179129
180130 /// <inheritdoc />
181131 public void Reset ( )
182132 {
183- _lock . EnterWriteLock ( ) ;
184- try
133+ lock ( _sync )
185134 {
186135 _pluginResolverFactories . Clear ( ) ;
187136 _userTypeMapper . Items . Clear ( ) ;
188137 HackyEnumTypeMappings . Clear ( ) ;
189- }
190- finally
191- {
192- _lock . ExitWriteLock ( ) ;
138+ _typeMappingOptions = null ;
193139 }
194140 }
195141
@@ -203,9 +149,25 @@ public INpgsqlNameTranslator DefaultNameTranslator
203149 /// <inheritdoc />
204150 public INpgsqlTypeMapper ConfigureJsonOptions ( JsonSerializerOptions serializerOptions )
205151 {
206- _jsonSerializerOptions = serializerOptions ;
207- // If JsonTypeInfoResolverFactory exists we replace it with a configured instance on the same index of the array.
208- ReplaceTypeInfoResolverFactory ( new JsonTypeInfoResolverFactory ( serializerOptions ) ) ;
152+ lock ( _sync )
153+ {
154+ _jsonSerializerOptions = serializerOptions ;
155+
156+ // If JsonTypeInfoResolverFactory exists we replace it with a configured instance on the same index of the array.
157+ var factory = new JsonTypeInfoResolverFactory ( serializerOptions ) ;
158+ var type = factory . GetType ( ) ;
159+
160+ for ( var i = 0 ; i < _pluginResolverFactories . Count ; i ++ )
161+ {
162+ if ( _pluginResolverFactories [ i ] . GetType ( ) == type )
163+ {
164+ _pluginResolverFactories [ i ] = factory ;
165+ break ;
166+ }
167+ }
168+
169+ _typeMappingOptions = null ;
170+ }
209171 return this ;
210172 }
211173
@@ -216,7 +178,9 @@ public INpgsqlTypeMapper EnableDynamicJson(
216178 Type [ ] ? jsonbClrTypes = null ,
217179 Type [ ] ? jsonClrTypes = null )
218180 {
219- AddTypeInfoResolverFactory ( new JsonDynamicTypeInfoResolverFactory ( jsonbClrTypes , jsonClrTypes , _jsonSerializerOptions ) ) ;
181+ // Use a re-entered lock to add the read of _jsonSerializerOptions to the total scope.
182+ lock ( _sync )
183+ AddTypeInfoResolverFactory ( new JsonDynamicTypeInfoResolverFactory ( jsonbClrTypes , jsonClrTypes , _jsonSerializerOptions ) ) ;
220184 return this ;
221185 }
222186
@@ -241,90 +205,68 @@ public INpgsqlTypeMapper EnableUnmappedTypes()
241205 /// <inheritdoc />
242206 public INpgsqlTypeMapper MapEnum < [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields ) ] TEnum > ( string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null ) where TEnum : struct , Enum
243207 {
244- _lock . EnterWriteLock ( ) ;
245- try
208+ lock ( _sync )
246209 {
247210 _userTypeMapper . MapEnum < TEnum > ( pgName , nameTranslator ) ;
248211
249- // Temporary hack for EFCore.PG enum mapping compat
212+ // Temporary hack for EFCore.PG enum mapping compat
250213 if ( _userTypeMapper . Items . FirstOrDefault ( i => i . ClrType == typeof ( TEnum ) ) is UserTypeMapping userTypeMapping )
251214 HackyEnumTypeMappings . Add ( new ( typeof ( TEnum ) , userTypeMapping . PgTypeName , nameTranslator ?? DefaultNameTranslator ) ) ;
252215
253- ResetTypeMappingCache ( ) ;
254-
216+ _typeMappingOptions = null ;
255217 return this ;
256218 }
257- finally
258- {
259- _lock . ExitWriteLock ( ) ;
260- }
261219 }
262220
263221 /// <inheritdoc />
264222 public bool UnmapEnum < [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields ) ] TEnum > ( string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null ) where TEnum : struct , Enum
265223 {
266- _lock . EnterWriteLock ( ) ;
267- try
224+ lock ( _sync )
268225 {
269226 var removed = _userTypeMapper . UnmapEnum < TEnum > ( pgName , nameTranslator ) ;
270227
271228 // Temporary hack for EFCore.PG enum mapping compat
272229 if ( removed && ( ( List < UserTypeMapping > ) _userTypeMapper . Items ) . FindIndex ( m => m . ClrType == typeof ( TEnum ) ) is > - 1 and var index )
273230 HackyEnumTypeMappings . RemoveAt ( index ) ;
274231
275- ResetTypeMappingCache ( ) ;
276-
232+ _typeMappingOptions = null ;
277233 return removed ;
278234 }
279- finally
280- {
281- _lock . ExitWriteLock ( ) ;
282- }
283235 }
284236
285237 /// <inheritdoc />
286238 [ RequiresDynamicCode ( "Calling MapEnum with a Type can require creating new generic types or methods. This may not work when AOT compiling." ) ]
287239 public INpgsqlTypeMapper MapEnum ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields | DynamicallyAccessedMemberTypes . PublicParameterlessConstructor ) ]
288240 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
289241 {
290- _lock . EnterWriteLock ( ) ;
291- try
242+ lock ( _sync )
292243 {
293244 _userTypeMapper . MapEnum ( clrType , pgName , nameTranslator ) ;
294245
295246 // Temporary hack for EFCore.PG enum mapping compat
296247 if ( _userTypeMapper . Items . FirstOrDefault ( i => i . ClrType == clrType ) is UserTypeMapping userTypeMapping )
297248 HackyEnumTypeMappings . Add ( new ( clrType , userTypeMapping . PgTypeName , nameTranslator ?? DefaultNameTranslator ) ) ;
298249
299- ResetTypeMappingCache ( ) ;
250+ _typeMappingOptions = null ;
300251 return this ;
301252 }
302- finally
303- {
304- _lock . ExitWriteLock ( ) ;
305- }
306253 }
307254
308255 /// <inheritdoc />
309256 public bool UnmapEnum ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicFields | DynamicallyAccessedMemberTypes . PublicParameterlessConstructor ) ]
310257 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
311258 {
312- _lock . EnterWriteLock ( ) ;
313- try
259+ lock ( _sync )
314260 {
315261 var removed = _userTypeMapper . UnmapEnum ( clrType , pgName , nameTranslator ) ;
316262
317263 // Temporary hack for EFCore.PG enum mapping compat
318264 if ( removed && ( ( List < UserTypeMapping > ) _userTypeMapper . Items ) . FindIndex ( m => m . ClrType == clrType ) is > - 1 and var index )
319265 HackyEnumTypeMappings . RemoveAt ( index ) ;
320266
321- ResetTypeMappingCache ( ) ;
267+ _typeMappingOptions = null ;
322268 return removed ;
323269 }
324- finally
325- {
326- _lock . ExitWriteLock ( ) ;
327- }
328270 }
329271
330272 /// <inheritdoc />
@@ -342,34 +284,24 @@ public bool UnmapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes
342284 public INpgsqlTypeMapper MapComposite ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicConstructors | DynamicallyAccessedMemberTypes . PublicProperties | DynamicallyAccessedMemberTypes . PublicFields ) ]
343285 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
344286 {
345- _lock . EnterWriteLock ( ) ;
346- try
287+ lock ( _sync )
347288 {
348289 _userTypeMapper . MapComposite ( clrType , pgName , nameTranslator ) ;
349- ResetTypeMappingCache ( ) ;
290+ _typeMappingOptions = null ;
350291 return this ;
351292 }
352- finally
353- {
354- _lock . ExitWriteLock ( ) ;
355- }
356293 }
357294
358295 /// <inheritdoc />
359296 [ 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." ) ]
360297 public bool UnmapComposite ( [ DynamicallyAccessedMembers ( DynamicallyAccessedMemberTypes . PublicConstructors | DynamicallyAccessedMemberTypes . PublicProperties | DynamicallyAccessedMemberTypes . PublicFields ) ]
361298 Type clrType , string ? pgName = null , INpgsqlNameTranslator ? nameTranslator = null )
362299 {
363- _lock . EnterWriteLock ( ) ;
364- try
300+ lock ( _sync )
365301 {
366302 var result = _userTypeMapper . UnmapComposite ( clrType , pgName , nameTranslator ) ;
367- ResetTypeMappingCache ( ) ;
303+ _typeMappingOptions = null ;
368304 return result ;
369305 }
370- finally
371- {
372- _lock . ExitWriteLock ( ) ;
373- }
374306 }
375307}
0 commit comments