1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Linq ;
3
4
using System . Runtime . Serialization ;
4
5
using System . Text ;
5
6
using NHibernate . Engine ;
@@ -20,12 +21,21 @@ public class QueryKey : IDeserializationCallback
20
21
private readonly object [ ] _values ;
21
22
private readonly int _firstRow = RowSelection . NoValue ;
22
23
private readonly int _maxRows = RowSelection . NoValue ;
24
+
25
+ // Sets and dictionaries are populated last during deserialization, causing them to be potentially empty
26
+ // during the deserialization callback. This causes them to be unreliable when used in hashcode computations.
27
+ // So better also serialize them as other structures and use those other structures in hashcode computation.
28
+ // We nonetheless need to also serialize them, because they are externally supplied and may use a custom
29
+ // comparer, implementation, ...
23
30
private readonly IDictionary < string , TypedValue > _namedParameters ;
24
31
private readonly ISet < FilterKey > _filters ;
32
+ private IEnumerable < KeyValuePair < string , TypedValue > > _serializedNamedParameters ;
33
+ private IEnumerable < FilterKey > _serializedFilters ;
34
+
25
35
private readonly CacheableResultTransformer _customTransformer ;
26
36
// hashcode may vary among processes, they cannot be stored and have to be re-computed after deserialization
27
37
[ NonSerialized ]
28
- private Lazy < int > _hashCode ;
38
+ private int ? _hashCode ;
29
39
30
40
private int [ ] _multiQueriesFirstRows ;
31
41
private int [ ] _multiQueriesMaxRows ;
@@ -61,8 +71,7 @@ public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, Query
61
71
_filters = filters ;
62
72
_customTransformer = customTransformer ;
63
73
64
- // No need to delay computation here, but we need the lazy for the deserialization case.
65
- _hashCode = new Lazy < int > ( ComputeHashCode ) ;
74
+ _hashCode = ComputeHashCode ( ) ;
66
75
}
67
76
68
77
public CacheableResultTransformer ResultTransformer
@@ -131,12 +140,32 @@ public override bool Equals(object other)
131
140
}
132
141
}
133
142
134
- if ( ! CollectionHelper . SetEquals ( _filters , that . _filters ) )
143
+ // On deserialization, _namedParameters and _filters may be not yet populated. Use their _serialization
144
+ // counterparts if they are defined.
145
+ if ( _serializedFilters != null || that . _serializedFilters != null )
146
+ {
147
+ var filters = _serializedFilters ?? _filters ;
148
+ var thatFilters = that . _serializedFilters ?? that . _filters ;
149
+ // BagEquals is less efficient than a SetEquals, but if the sets are using a custom comparer, it
150
+ // will cause less issues than rebuilding sets without the custom comparer.
151
+ if ( ! CollectionHelper . BagEquals ( filters , thatFilters ) )
152
+ return false ;
153
+ }
154
+ else if ( ! CollectionHelper . SetEquals ( _filters , that . _filters ) )
135
155
{
136
156
return false ;
137
157
}
138
158
139
- if ( ! CollectionHelper . DictionaryEquals ( _namedParameters , that . _namedParameters ) )
159
+ if ( _serializedNamedParameters != null || that . _serializedNamedParameters != null )
160
+ {
161
+ var namedParameters = _serializedNamedParameters ?? _namedParameters ;
162
+ var thatNamedParameters = that . _serializedNamedParameters ?? that . _namedParameters ;
163
+ // BagEquals is less efficient than a DictionaryEquals, but if the dictionaries are using a custom
164
+ // comparer, it will cause less issues than rebuilding the dictionaries without the custom comparer.
165
+ if ( ! CollectionHelper . BagEquals ( namedParameters , thatNamedParameters , NamedParameterComparer . Instance ) )
166
+ return false ;
167
+ }
168
+ else if ( ! CollectionHelper . DictionaryEquals ( _namedParameters , that . _namedParameters ) )
140
169
{
141
170
return false ;
142
171
}
@@ -154,16 +183,27 @@ public override bool Equals(object other)
154
183
155
184
public override int GetHashCode ( )
156
185
{
157
- // If the object is put in a set or dictionary during deserialization, the lazy will be null, and
158
- // we have to compute the hashcode on the fly.
159
- return _hashCode ? . Value ?? ComputeHashCodeOnDeserialization ( ) ;
186
+ // If the object is put in a set or dictionary during deserialization, the hashcode will not yet be
187
+ // computed. Compute the hashcode on the fly. So long as this happens only during deserialization, there
188
+ // will be no thread safety issues. For the hashcode to be always defined after deserialization, the
189
+ // deserialization callback is used.
190
+ return _hashCode ?? ComputeHashCode ( ) ;
160
191
}
161
192
162
- private int ComputeHashCodeOnDeserialization ( )
193
+ /// <inheritdoc />
194
+ public void OnDeserialization ( object sender )
163
195
{
164
- ( _filters as IDeserializationCallback ) . OnDeserialization ( this ) ;
165
- ( _namedParameters as IDeserializationCallback ) . OnDeserialization ( this ) ;
166
- return ComputeHashCode ( ) ;
196
+ _hashCode = ComputeHashCode ( ) ;
197
+
198
+ _serializedNamedParameters = null ;
199
+ _serializedFilters = null ;
200
+ }
201
+
202
+ [ OnSerializing ]
203
+ private void OnSerializing ( StreamingContext context )
204
+ {
205
+ _serializedNamedParameters = _namedParameters ? . ToArray ( ) ;
206
+ _serializedFilters = _filters ? . ToArray ( ) ;
167
207
}
168
208
169
209
public int ComputeHashCode ( )
@@ -174,7 +214,10 @@ public int ComputeHashCode()
174
214
result = 37 * result + _firstRow . GetHashCode ( ) ;
175
215
result = 37 * result + _maxRows . GetHashCode ( ) ;
176
216
177
- result = 37 * result + ( _namedParameters == null ? 0 : CollectionHelper . GetHashCode ( _namedParameters , NamedParameterComparer . Instance ) ) ;
217
+ // On deserialization, _namedParameters may be not yet populated. Use _serializedNamedParameters
218
+ // if it is defined.
219
+ var namedParameters = _serializedNamedParameters ?? _namedParameters ;
220
+ result = 37 * result + ( namedParameters == null ? 0 : CollectionHelper . GetHashCode ( namedParameters , NamedParameterComparer . Instance ) ) ;
178
221
179
222
for ( int i = 0 ; i < _types . Length ; i ++ )
180
223
{
@@ -201,12 +244,12 @@ public int ComputeHashCode()
201
244
}
202
245
}
203
246
204
- if ( _filters != null )
247
+ // On deserialization, _filters may be not yet populated. Use _serializedFilters
248
+ // if it is defined.
249
+ var filters = _serializedFilters ?? _filters ;
250
+ if ( filters != null )
205
251
{
206
- foreach ( var filter in _filters )
207
- {
208
- result = 37 * result + filter . GetHashCode ( ) ;
209
- }
252
+ result = 37 * result + CollectionHelper . GetHashCode ( filters ) ;
210
253
}
211
254
212
255
result = 37 * result + ( _customTransformer == null ? 0 : _customTransformer . GetHashCode ( ) ) ;
@@ -215,16 +258,6 @@ public int ComputeHashCode()
215
258
}
216
259
}
217
260
218
- /// <inheritdoc />
219
- public void OnDeserialization ( object sender )
220
- {
221
- // No attempt to compute directly here: the computation depends on a complex graph of objects,
222
- // including hashsets and dictionaries, which are not yet populated. We could force them to get
223
- // populated by explicitly calling OnDeserialization on them, but then, we would also have to iterate
224
- // them and do the same on any dictionary or set contained by their elements (like FilterKey).
225
- _hashCode = new Lazy < int > ( ComputeHashCode ) ;
226
- }
227
-
228
261
public override string ToString ( )
229
262
{
230
263
StringBuilder buf = new StringBuilder ( )
0 commit comments