11using System ;
22using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Runtime . Serialization ;
35using System . Text ;
46using NHibernate . Engine ;
57using NHibernate . Impl ;
1113namespace NHibernate . Cache
1214{
1315 [ Serializable ]
14- public class QueryKey
16+ public class QueryKey : IDeserializationCallback
1517 {
1618 private readonly ISessionFactoryImplementor _factory ;
1719 private readonly SqlString _sqlQueryString ;
1820 private readonly IType [ ] _types ;
1921 private readonly object [ ] _values ;
2022 private readonly int _firstRow = RowSelection . NoValue ;
2123 private readonly int _maxRows = RowSelection . NoValue ;
22- private readonly IDictionary < string , TypedValue > _namedParameters ;
23- private readonly ISet < FilterKey > _filters ;
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 or equals
27+ // computations. These computations occur during the deserialization callback for example when another
28+ // serialized set or dictionary contain an instance of this class.
29+ // So better serialize them as other structures, so long for Equals implementation which actually needs a
30+ // dictionary and set.
31+ private readonly KeyValuePair < string , TypedValue > [ ] _namedParameters ;
32+ private readonly FilterKey [ ] _filters ;
33+
2434 private readonly CacheableResultTransformer _customTransformer ;
25- private readonly int _hashCode ;
35+ // hashcode may vary among processes, they cannot be stored and have to be re-computed after deserialization
36+ [ NonSerialized ]
37+ private int ? _hashCode ;
2638
2739 private int [ ] _multiQueriesFirstRows ;
2840 private int [ ] _multiQueriesMaxRows ;
2941
30-
3142 /// <summary>
3243 /// Initializes a new instance of the <see cref="QueryKey"/> class.
3344 /// </summary>
@@ -55,9 +66,11 @@ public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, Query
5566 _firstRow = RowSelection . NoValue ;
5667 _maxRows = RowSelection . NoValue ;
5768 }
58- _namedParameters = queryParameters . NamedParameters ;
59- _filters = filters ;
69+
70+ _namedParameters = queryParameters . NamedParameters ? . ToArray ( ) ;
71+ _filters = filters ? . ToArray ( ) ;
6072 _customTransformer = customTransformer ;
73+
6174 _hashCode = ComputeHashCode ( ) ;
6275 }
6376
@@ -127,15 +140,14 @@ public override bool Equals(object other)
127140 }
128141 }
129142
130- if ( ! CollectionHelper . SetEquals ( _filters , that . _filters ) )
131- {
143+ // BagEquals is less efficient than a SetEquals or DictionaryEquals, but serializing dictionaries causes
144+ // issues on deserialization if GetHashCode or Equals are called in its deserialization callback. And
145+ // building sets or dictionaries on the fly will in most cases be worst than BagEquals, unless re-coding
146+ // its short-circuits.
147+ if ( ! CollectionHelper . BagEquals ( _filters , that . _filters ) )
132148 return false ;
133- }
134-
135- if ( ! CollectionHelper . DictionaryEquals ( _namedParameters , that . _namedParameters ) )
136- {
149+ if ( ! CollectionHelper . BagEquals ( _namedParameters , that . _namedParameters , NamedParameterComparer . Instance ) )
137150 return false ;
138- }
139151
140152 if ( ! CollectionHelper . SequenceEquals < int > ( _multiQueriesFirstRows , that . _multiQueriesFirstRows ) )
141153 {
@@ -150,7 +162,17 @@ public override bool Equals(object other)
150162
151163 public override int GetHashCode ( )
152164 {
153- return _hashCode ;
165+ // If the object is put in a set or dictionary during deserialization, the hashcode will not yet be
166+ // computed. Compute the hashcode on the fly. So long as this happens only during deserialization, there
167+ // will be no thread safety issues. For the hashcode to be always defined after deserialization, the
168+ // deserialization callback is used.
169+ return _hashCode ?? ComputeHashCode ( ) ;
170+ }
171+
172+ /// <inheritdoc />
173+ public void OnDeserialization ( object sender )
174+ {
175+ _hashCode = ComputeHashCode ( ) ;
154176 }
155177
156178 public int ComputeHashCode ( )
@@ -161,7 +183,9 @@ public int ComputeHashCode()
161183 result = 37 * result + _firstRow . GetHashCode ( ) ;
162184 result = 37 * result + _maxRows . GetHashCode ( ) ;
163185
164- result = 37 * result + ( _namedParameters == null ? 0 : CollectionHelper . GetHashCode ( _namedParameters , NamedParameterComparer . Instance ) ) ;
186+ result = 37 * result + ( _namedParameters == null
187+ ? 0
188+ : CollectionHelper . GetHashCode ( _namedParameters , NamedParameterComparer . Instance ) ) ;
165189
166190 for ( int i = 0 ; i < _types . Length ; i ++ )
167191 {
@@ -190,10 +214,7 @@ public int ComputeHashCode()
190214
191215 if ( _filters != null )
192216 {
193- foreach ( object filter in _filters )
194- {
195- result = 37 * result + filter . GetHashCode ( ) ;
196- }
217+ result = 37 * result + CollectionHelper . GetHashCode ( _filters ) ;
197218 }
198219
199220 result = 37 * result + ( _customTransformer == null ? 0 : _customTransformer . GetHashCode ( ) ) ;
0 commit comments