1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Linq ;
4
+ using System . Runtime . Serialization ;
3
5
using System . Text ;
4
6
using NHibernate . Engine ;
5
7
using NHibernate . Impl ;
11
13
namespace NHibernate . Cache
12
14
{
13
15
[ Serializable ]
14
- public class QueryKey
16
+ public class QueryKey : IDeserializationCallback
15
17
{
16
18
private readonly ISessionFactoryImplementor _factory ;
17
19
private readonly SqlString _sqlQueryString ;
18
20
private readonly IType [ ] _types ;
19
21
private readonly object [ ] _values ;
20
22
private readonly int _firstRow = RowSelection . NoValue ;
21
23
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
+
24
34
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 ;
26
38
27
39
private int [ ] _multiQueriesFirstRows ;
28
40
private int [ ] _multiQueriesMaxRows ;
29
41
30
-
31
42
/// <summary>
32
43
/// Initializes a new instance of the <see cref="QueryKey"/> class.
33
44
/// </summary>
@@ -55,9 +66,11 @@ public QueryKey(ISessionFactoryImplementor factory, SqlString queryString, Query
55
66
_firstRow = RowSelection . NoValue ;
56
67
_maxRows = RowSelection . NoValue ;
57
68
}
58
- _namedParameters = queryParameters . NamedParameters ;
59
- _filters = filters ;
69
+
70
+ _namedParameters = queryParameters . NamedParameters ? . ToArray ( ) ;
71
+ _filters = filters ? . ToArray ( ) ;
60
72
_customTransformer = customTransformer ;
73
+
61
74
_hashCode = ComputeHashCode ( ) ;
62
75
}
63
76
@@ -127,15 +140,14 @@ public override bool Equals(object other)
127
140
}
128
141
}
129
142
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 ) )
132
148
return false ;
133
- }
134
-
135
- if ( ! CollectionHelper . DictionaryEquals ( _namedParameters , that . _namedParameters ) )
136
- {
149
+ if ( ! CollectionHelper . BagEquals ( _namedParameters , that . _namedParameters , NamedParameterComparer . Instance ) )
137
150
return false ;
138
- }
139
151
140
152
if ( ! CollectionHelper . SequenceEquals < int > ( _multiQueriesFirstRows , that . _multiQueriesFirstRows ) )
141
153
{
@@ -150,7 +162,17 @@ public override bool Equals(object other)
150
162
151
163
public override int GetHashCode ( )
152
164
{
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 ( ) ;
154
176
}
155
177
156
178
public int ComputeHashCode ( )
@@ -161,7 +183,9 @@ public int ComputeHashCode()
161
183
result = 37 * result + _firstRow . GetHashCode ( ) ;
162
184
result = 37 * result + _maxRows . GetHashCode ( ) ;
163
185
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 ) ) ;
165
189
166
190
for ( int i = 0 ; i < _types . Length ; i ++ )
167
191
{
@@ -190,10 +214,7 @@ public int ComputeHashCode()
190
214
191
215
if ( _filters != null )
192
216
{
193
- foreach ( object filter in _filters )
194
- {
195
- result = 37 * result + filter . GetHashCode ( ) ;
196
- }
217
+ result = 37 * result + CollectionHelper . GetHashCode ( _filters ) ;
197
218
}
198
219
199
220
result = 37 * result + ( _customTransformer == null ? 0 : _customTransformer . GetHashCode ( ) ) ;
0 commit comments