@@ -20,18 +20,18 @@ public sealed class NpgsqlParameterCollection : DbParameterCollection, IList<Npg
2020
2121 internal List < NpgsqlParameter > InternalList { get ; } = new ( 5 ) ;
2222#if DEBUG
23- internal static bool CaseInsensitiveCompatMode ;
23+ internal static bool TwoPassCompatMode ;
2424#else
25- internal static readonly bool CaseInsensitiveCompatMode ;
25+ internal static readonly bool TwoPassCompatMode ;
2626#endif
2727
2828 static NpgsqlParameterCollection ( )
29- => CaseInsensitiveCompatMode = AppContext . TryGetSwitch ( "Npgsql.EnableLegacyCaseInsensitiveDbParameters" , out var enabled )
30- && enabled ;
29+ => TwoPassCompatMode = AppContext . TryGetSwitch ( "Npgsql.EnableLegacyCaseInsensitiveDbParameters" , out var enabled )
30+ && enabled ;
3131
32- // Dictionary lookups for GetValue to improve performance
33- Dictionary < string , int > ? _lookup ;
32+ // Dictionary lookups for GetValue to improve performance. _caseSensitiveLookup is only ever used in legacy two-pass mode.
3433 Dictionary < string , int > ? _caseInsensitiveLookup ;
34+ Dictionary < string , int > ? _caseSensitiveLookup ;
3535
3636 /// <summary>
3737 /// Initializes a new instance of the NpgsqlParameterCollection class.
@@ -42,33 +42,39 @@ internal NpgsqlParameterCollection() {}
4242
4343 void LookupClear ( )
4444 {
45- _lookup ? . Clear ( ) ;
4645 _caseInsensitiveLookup ? . Clear ( ) ;
46+ _caseSensitiveLookup ? . Clear ( ) ;
4747 }
4848
4949 void LookupAdd ( string name , int index )
5050 {
51- if ( _lookup is not null && ! _lookup . ContainsKey ( name ) )
52- _lookup [ name ] = index ;
51+ if ( _caseInsensitiveLookup is null )
52+ return ;
53+
54+ if ( TwoPassCompatMode && ! _caseSensitiveLookup ! . ContainsKey ( name ) )
55+ _caseSensitiveLookup [ name ] = index ;
5356
54- if ( CaseInsensitiveCompatMode && _caseInsensitiveLookup is not null && ! _caseInsensitiveLookup . ContainsKey ( name ) )
57+ if ( ! _caseInsensitiveLookup . ContainsKey ( name ) )
5558 _caseInsensitiveLookup [ name ] = index ;
5659 }
5760
5861 void LookupInsert ( string name , int index )
5962 {
60- if ( _lookup is not null && ( ! _lookup . TryGetValue ( name , out var indexCs ) || index < indexCs ) )
63+ if ( _caseInsensitiveLookup is null )
64+ return ;
65+
66+ if ( TwoPassCompatMode &&
67+ ( ! _caseSensitiveLookup ! . TryGetValue ( name , out var indexCs ) || index < indexCs ) )
6168 {
62- foreach ( var kv in _lookup )
69+ foreach ( var kv in _caseSensitiveLookup )
6370 {
6471 if ( index <= kv . Value )
65- _lookup [ kv . Key ] = kv . Value + 1 ;
72+ _caseSensitiveLookup [ kv . Key ] = kv . Value + 1 ;
6673 }
67- _lookup [ name ] = index ;
74+ _caseSensitiveLookup [ name ] = index ;
6875 }
6976
70- if ( CaseInsensitiveCompatMode && _caseInsensitiveLookup is not null &&
71- ( ! _caseInsensitiveLookup . TryGetValue ( name , out var indexCi ) || index < indexCi ) )
77+ if ( ! _caseInsensitiveLookup . TryGetValue ( name , out var indexCi ) || index < indexCi )
7278 {
7379 foreach ( var kv in _caseInsensitiveLookup )
7480 {
@@ -81,26 +87,27 @@ void LookupInsert(string name, int index)
8187
8288 void LookupRemove ( string name , int index )
8389 {
84- if ( _lookup is not null && _lookup . ContainsKey ( name ) )
90+ if ( _caseInsensitiveLookup is null )
91+ return ;
92+
93+ if ( TwoPassCompatMode && _caseSensitiveLookup ! . Remove ( name ) )
8594 {
86- _lookup . Remove ( name ) ;
87- foreach ( var kv in _lookup )
95+ foreach ( var kv in _caseSensitiveLookup )
8896 {
8997 if ( index < kv . Value )
90- _lookup [ kv . Key ] = kv . Value - 1 ;
98+ _caseSensitiveLookup [ kv . Key ] = kv . Value - 1 ;
9199 }
92100 }
93101
94- if ( CaseInsensitiveCompatMode && _caseInsensitiveLookup is not null &&
95- _caseInsensitiveLookup . TryGetValue ( name , out var indexCi ) && indexCi == index )
102+ if ( _caseInsensitiveLookup . Remove ( name ) )
96103 {
97- _caseInsensitiveLookup . Remove ( name ) ;
98104 foreach ( var kv in _caseInsensitiveLookup )
99105 {
100106 if ( index < kv . Value )
101107 _caseInsensitiveLookup [ kv . Key ] = kv . Value - 1 ;
102108 }
103109
110+ // Fix-up the case-insensitive lookup to point to the next match, if any.
104111 for ( var i = 0 ; i < InternalList . Count ; i ++ )
105112 {
106113 var value = InternalList [ i ] ;
@@ -111,11 +118,12 @@ void LookupRemove(string name, int index)
111118 }
112119 }
113120 }
121+
114122 }
115123
116124 void LookupChangeName ( NpgsqlParameter parameter , string oldName , string oldTrimmedName , int index )
117125 {
118- if ( string . Equals ( oldTrimmedName , parameter . TrimmedName , CaseInsensitiveCompatMode ? StringComparison . OrdinalIgnoreCase : StringComparison . Ordinal ) )
126+ if ( string . Equals ( oldTrimmedName , parameter . TrimmedName , StringComparison . OrdinalIgnoreCase ) )
119127 return ;
120128
121129 if ( oldName . Length != 0 )
@@ -124,9 +132,13 @@ void LookupChangeName(NpgsqlParameter parameter, string oldName, string oldTrimm
124132 LookupAdd ( parameter . TrimmedName , index ) ;
125133 }
126134
127- internal void ChangeParameterName ( NpgsqlParameter parameter , string oldName , string oldTrimmedName )
135+ internal void ChangeParameterName ( NpgsqlParameter parameter , string ? value )
128136 {
129- if ( _lookup is null || _lookup . Count == 0 )
137+ var oldName = parameter . ParameterName ;
138+ var oldTrimmedName = parameter . TrimmedName ;
139+ parameter . ChangeParameterName ( value ) ;
140+
141+ if ( _caseInsensitiveLookup is null || _caseInsensitiveLookup . Count == 0 )
130142 return ;
131143
132144 var index = IndexOf ( parameter ) ;
@@ -169,6 +181,9 @@ internal void ChangeParameterName(NpgsqlParameter parameter, string oldName, str
169181 if ( index == - 1 )
170182 throw new ArgumentException ( "Parameter not found" ) ;
171183
184+ if ( ! string . Equals ( parameterName , value . TrimmedName , StringComparison . OrdinalIgnoreCase ) )
185+ throw new ArgumentException ( "Parameter name must be a case-insensitive match with the property 'ParameterName' on the given NpgsqlParameter" , nameof ( parameterName ) ) ;
186+
172187 var oldValue = InternalList [ index ] ;
173188 LookupChangeName ( value , oldValue . ParameterName , oldValue . TrimmedName , index ) ;
174189
@@ -193,7 +208,7 @@ internal void ChangeParameterName(NpgsqlParameter parameter, string oldName, str
193208
194209 var oldValue = InternalList [ index ] ;
195210
196- if ( oldValue == value )
211+ if ( ReferenceEquals ( oldValue , value ) )
197212 return ;
198213
199214 LookupChangeName ( value , oldValue . ParameterName , oldValue . TrimmedName , index ) ;
@@ -348,38 +363,45 @@ public override int IndexOf(string parameterName)
348363 // For string equality this is the case after ~3 items so we take a decent compromise going with 5.
349364 if ( LookupEnabled && parameterName . Length != 0 )
350365 {
351- if ( _lookup is null )
366+ if ( _caseInsensitiveLookup is null )
352367 BuildLookup ( ) ;
353368
354- if ( _lookup ! . TryGetValue ( parameterName , out var indexCs ) )
369+ if ( TwoPassCompatMode && _caseSensitiveLookup ! . TryGetValue ( parameterName , out var indexCs ) )
355370 return indexCs ;
356371
357- if ( CaseInsensitiveCompatMode && _caseInsensitiveLookup ! . TryGetValue ( parameterName , out var indexCi ) )
372+ if ( _caseInsensitiveLookup ! . TryGetValue ( parameterName , out var indexCi ) )
358373 return indexCi ;
359374
360375 return - 1 ;
361376 }
362377
378+ // Start with case-sensitive search in two pass mode.
379+ if ( TwoPassCompatMode )
380+ {
381+ for ( var i = 0 ; i < InternalList . Count ; i ++ )
382+ {
383+ var name = InternalList [ i ] . TrimmedName ;
384+ if ( string . Equals ( parameterName , InternalList [ i ] . TrimmedName ) )
385+ return i ;
386+ }
387+ }
388+
389+ // Then do case-insensitive search.
363390 for ( var i = 0 ; i < InternalList . Count ; i ++ )
364391 {
365392 var name = InternalList [ i ] . TrimmedName ;
366- if ( ReferenceEquals ( parameterName , name ) || string . Equals ( parameterName , name ) )
393+ if ( ReferenceEquals ( parameterName , name ) || string . Equals ( parameterName , name , StringComparison . OrdinalIgnoreCase ) )
367394 return i ;
368395 }
369396
370- // Fall back to a case-insensitive search.
371- if ( CaseInsensitiveCompatMode )
372- for ( var i = 0 ; i < InternalList . Count ; i ++ )
373- if ( string . Equals ( parameterName , InternalList [ i ] . TrimmedName , StringComparison . OrdinalIgnoreCase ) )
374- return i ;
375-
376397 return - 1 ;
377398
378399 void BuildLookup ( )
379400 {
380- _lookup = new Dictionary < string , int > ( InternalList . Count , StringComparer . Ordinal ) ;
381- if ( CaseInsensitiveCompatMode )
382- _caseInsensitiveLookup = new Dictionary < string , int > ( InternalList . Count , StringComparer . OrdinalIgnoreCase ) ;
401+ if ( TwoPassCompatMode )
402+ _caseSensitiveLookup = new Dictionary < string , int > ( InternalList . Count , StringComparer . Ordinal ) ;
403+
404+ _caseInsensitiveLookup = new Dictionary < string , int > ( InternalList . Count , StringComparer . OrdinalIgnoreCase ) ;
383405
384406 for ( var i = 0 ; i < InternalList . Count ; i ++ )
385407 {
@@ -640,7 +662,13 @@ internal void CloneTo(NpgsqlParameterCollection other)
640662 newParam . Collection = this ;
641663 other . InternalList . Add ( newParam ) ;
642664 }
643- other . _lookup = _lookup ;
665+
666+ if ( LookupEnabled )
667+ {
668+ other . _caseInsensitiveLookup = new Dictionary < string , int > ( _caseInsensitiveLookup ! , StringComparer . OrdinalIgnoreCase ) ;
669+ if ( TwoPassCompatMode )
670+ other . _caseSensitiveLookup = new Dictionary < string , int > ( _caseSensitiveLookup ! , StringComparer . Ordinal ) ;
671+ }
644672 }
645673
646674 internal void ValidateAndBind ( ConnectorTypeMapper typeMapper )
0 commit comments