5
5
using NHibernate . Persister . Entity ;
6
6
using NHibernate . Util ;
7
7
using System . Collections . Generic ;
8
+ using Iesi . Collections . Generic ;
8
9
9
10
namespace NHibernate . Engine
10
11
{
11
12
public partial class BatchFetchQueue
12
13
{
13
- private static readonly object Marker = new object ( ) ;
14
+ private static readonly INHibernateLogger log = NHibernateLogger . For ( typeof ( BatchFetchQueue ) ) ;
14
15
15
16
/// <summary>
16
- /// Defines a sequence of <see cref="EntityKey" /> elements that are currently
17
- /// eligible for batch fetching .
17
+ /// Used to hold information about the entities that are currently eligible for batch-fetching. Ultimately
18
+ /// used by <see cref="GetEntityBatch" /> to build entity load batches .
18
19
/// </summary>
19
20
/// <remarks>
20
- /// Even though this is a map, we only use the keys. A map was chosen in
21
- /// order to utilize a <see cref="LinkedHashMap{K, V}" /> to maintain sequencing
22
- /// as well as uniqueness.
21
+ /// A Map structure is used to segment the keys by entity type since loading can only be done for a particular entity
22
+ /// type at a time.
23
23
/// </remarks>
24
- private readonly IDictionary < EntityKey , object > batchLoadableEntityKeys = new LinkedHashMap < EntityKey , object > ( 8 ) ;
24
+ private readonly IDictionary < string , LinkedHashSet < EntityKey > > batchLoadableEntityKeys = new Dictionary < string , LinkedHashSet < EntityKey > > ( 8 ) ;
25
25
26
26
/// <summary>
27
27
/// A map of <see cref="SubselectFetch">subselect-fetch descriptors</see>
@@ -30,6 +30,7 @@ public partial class BatchFetchQueue
30
30
/// </summary>
31
31
private readonly IDictionary < EntityKey , SubselectFetch > subselectsByEntityKey = new Dictionary < EntityKey , SubselectFetch > ( 8 ) ;
32
32
33
+ private readonly IDictionary < string , LinkedHashMap < CollectionEntry , IPersistentCollection > > batchLoadableCollections = new Dictionary < string , LinkedHashMap < CollectionEntry , IPersistentCollection > > ( 8 ) ;
33
34
/// <summary>
34
35
/// The owning persistence context.
35
36
/// </summary>
@@ -50,6 +51,7 @@ public BatchFetchQueue(IPersistenceContext context)
50
51
public void Clear ( )
51
52
{
52
53
batchLoadableEntityKeys . Clear ( ) ;
54
+ batchLoadableCollections . Clear ( ) ;
53
55
subselectsByEntityKey . Clear ( ) ;
54
56
}
55
57
@@ -113,7 +115,12 @@ public void AddBatchLoadableEntityKey(EntityKey key)
113
115
{
114
116
if ( key . IsBatchLoadable )
115
117
{
116
- batchLoadableEntityKeys [ key ] = Marker ;
118
+ if ( ! batchLoadableEntityKeys . TryGetValue ( key . EntityName , out var set ) )
119
+ {
120
+ set = new LinkedHashSet < EntityKey > ( ) ;
121
+ batchLoadableEntityKeys . Add ( key . EntityName , set ) ;
122
+ }
123
+ set . Add ( key ) ;
117
124
}
118
125
}
119
126
@@ -125,7 +132,44 @@ public void AddBatchLoadableEntityKey(EntityKey key)
125
132
public void RemoveBatchLoadableEntityKey ( EntityKey key )
126
133
{
127
134
if ( key . IsBatchLoadable )
128
- batchLoadableEntityKeys . Remove ( key ) ;
135
+ {
136
+ if ( batchLoadableEntityKeys . TryGetValue ( key . EntityName , out var set ) )
137
+ {
138
+ set . Remove ( key ) ;
139
+ }
140
+ }
141
+ }
142
+
143
+ /// <summary>
144
+ /// If a CollectionEntry represents a batch loadable collection, add
145
+ /// it to the queue.
146
+ /// </summary>
147
+ /// <param name="collection"></param>
148
+ /// <param name="ce"></param>
149
+ public void AddBatchLoadableCollection ( IPersistentCollection collection , CollectionEntry ce )
150
+ {
151
+ var persister = ce . LoadedPersister ;
152
+
153
+ if ( ! batchLoadableCollections . TryGetValue ( persister . Role , out var map ) )
154
+ {
155
+ map = new LinkedHashMap < CollectionEntry , IPersistentCollection > ( ) ;
156
+ batchLoadableCollections . Add ( persister . Role , map ) ;
157
+ }
158
+ map [ ce ] = collection ;
159
+ }
160
+
161
+ /// <summary>
162
+ /// After a collection was initialized or evicted, we don't
163
+ /// need to batch fetch it anymore, remove it from the queue
164
+ /// if necessary
165
+ /// </summary>
166
+ /// <param name="ce"></param>
167
+ public void RemoveBatchLoadableCollection ( CollectionEntry ce )
168
+ {
169
+ if ( batchLoadableCollections . TryGetValue ( ce . LoadedPersister . Role , out var map ) )
170
+ {
171
+ map . Remove ( ce ) ;
172
+ }
129
173
}
130
174
131
175
/// <summary>
@@ -143,22 +187,33 @@ public object[] GetCollectionBatch(ICollectionPersister collectionPersister, obj
143
187
int end = - 1 ;
144
188
bool checkForEnd = false ;
145
189
146
- // this only works because collection entries are kept in a sequenced
147
- // map by persistence context (maybe we should do like entities and
148
- // keep a separate sequences set...)
149
- foreach ( DictionaryEntry me in context . CollectionEntries )
190
+ if ( batchLoadableCollections . TryGetValue ( collectionPersister . Role , out var map ) )
150
191
{
151
- CollectionEntry ce = ( CollectionEntry ) me . Value ;
152
- IPersistentCollection collection = ( IPersistentCollection ) me . Key ;
153
- if ( ! collection . WasInitialized && ce . LoadedPersister == collectionPersister )
192
+ foreach ( KeyValuePair < CollectionEntry , IPersistentCollection > me in map )
154
193
{
194
+ var ce = me . Key ;
195
+ var collection = me . Value ;
196
+ if ( ce . LoadedKey == null )
197
+ {
198
+ // the LoadedKey of the CollectionEntry might be null as it might have been reset to null
199
+ // (see for example Collections.ProcessDereferencedCollection()
200
+ // and CollectionEntry.AfterAction())
201
+ // though we clear the queue on flush, it seems like a good idea to guard
202
+ // against potentially null LoadedKey:s
203
+ continue ;
204
+ }
205
+
206
+ if ( collection . WasInitialized )
207
+ {
208
+ log . Warn ( "Encountered initialized collection in BatchFetchQueue, this should not happen." ) ;
209
+ continue ;
210
+ }
211
+
155
212
if ( checkForEnd && i == end )
156
213
{
157
214
return keys ; //the first key found after the given key
158
215
}
159
216
160
- //if ( end == -1 && count > batchSize*10 ) return keys; //try out ten batches, max
161
-
162
217
bool isEqual = collectionPersister . KeyType . IsEqual ( id , ce . LoadedKey , collectionPersister . Factory ) ;
163
218
164
219
if ( isEqual )
@@ -182,6 +237,7 @@ public object[] GetCollectionBatch(ICollectionPersister collectionPersister, obj
182
237
}
183
238
}
184
239
}
240
+
185
241
return keys ; //we ran out of keys to try
186
242
}
187
243
@@ -194,17 +250,17 @@ public object[] GetCollectionBatch(ICollectionPersister collectionPersister, obj
194
250
/// <param name="id">The identifier of the entity currently demanding load.</param>
195
251
/// <param name="batchSize">The maximum number of keys to return</param>
196
252
/// <returns>an array of identifiers, of length batchSize (possibly padded with nulls)</returns>
197
- public object [ ] GetEntityBatch ( IEntityPersister persister , object id , int batchSize )
253
+ public object [ ] GetEntityBatch ( IEntityPersister persister , object id , int batchSize )
198
254
{
199
255
object [ ] ids = new object [ batchSize ] ;
200
256
ids [ 0 ] = id ; //first element of array is reserved for the actual instance we are loading!
201
257
int i = 1 ;
202
258
int end = - 1 ;
203
259
bool checkForEnd = false ;
204
260
205
- foreach ( EntityKey key in batchLoadableEntityKeys . Keys )
261
+ if ( batchLoadableEntityKeys . TryGetValue ( persister . EntityName , out var set ) )
206
262
{
207
- if ( key . EntityName . Equals ( persister . EntityName ) )
263
+ foreach ( var key in set )
208
264
{
209
265
//TODO: this needn't exclude subclasses...
210
266
if ( checkForEnd && i == end )
0 commit comments