55using NHibernate . Persister . Entity ;
66using NHibernate . Util ;
77using System . Collections . Generic ;
8+ using Iesi . Collections . Generic ;
89
910namespace NHibernate . Engine
1011{
1112 public partial class BatchFetchQueue
1213 {
13- private static readonly object Marker = new object ( ) ;
14+ private static readonly INHibernateLogger log = NHibernateLogger . For ( typeof ( BatchFetchQueue ) ) ;
1415
1516 /// <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 .
1819 /// </summary>
1920 /// <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.
2323 /// </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 ) ;
2525
2626 /// <summary>
2727 /// A map of <see cref="SubselectFetch">subselect-fetch descriptors</see>
@@ -30,6 +30,7 @@ public partial class BatchFetchQueue
3030 /// </summary>
3131 private readonly IDictionary < EntityKey , SubselectFetch > subselectsByEntityKey = new Dictionary < EntityKey , SubselectFetch > ( 8 ) ;
3232
33+ private readonly IDictionary < string , LinkedHashMap < CollectionEntry , IPersistentCollection > > batchLoadableCollections = new Dictionary < string , LinkedHashMap < CollectionEntry , IPersistentCollection > > ( 8 ) ;
3334 /// <summary>
3435 /// The owning persistence context.
3536 /// </summary>
@@ -50,6 +51,7 @@ public BatchFetchQueue(IPersistenceContext context)
5051 public void Clear ( )
5152 {
5253 batchLoadableEntityKeys . Clear ( ) ;
54+ batchLoadableCollections . Clear ( ) ;
5355 subselectsByEntityKey . Clear ( ) ;
5456 }
5557
@@ -113,7 +115,12 @@ public void AddBatchLoadableEntityKey(EntityKey key)
113115 {
114116 if ( key . IsBatchLoadable )
115117 {
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 ) ;
117124 }
118125 }
119126
@@ -125,7 +132,44 @@ public void AddBatchLoadableEntityKey(EntityKey key)
125132 public void RemoveBatchLoadableEntityKey ( EntityKey key )
126133 {
127134 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+ }
129173 }
130174
131175 /// <summary>
@@ -143,22 +187,33 @@ public object[] GetCollectionBatch(ICollectionPersister collectionPersister, obj
143187 int end = - 1 ;
144188 bool checkForEnd = false ;
145189
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 ) )
150191 {
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 )
154193 {
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+
155212 if ( checkForEnd && i == end )
156213 {
157214 return keys ; //the first key found after the given key
158215 }
159216
160- //if ( end == -1 && count > batchSize*10 ) return keys; //try out ten batches, max
161-
162217 bool isEqual = collectionPersister . KeyType . IsEqual ( id , ce . LoadedKey , collectionPersister . Factory ) ;
163218
164219 if ( isEqual )
@@ -182,6 +237,7 @@ public object[] GetCollectionBatch(ICollectionPersister collectionPersister, obj
182237 }
183238 }
184239 }
240+
185241 return keys ; //we ran out of keys to try
186242 }
187243
@@ -194,17 +250,17 @@ public object[] GetCollectionBatch(ICollectionPersister collectionPersister, obj
194250 /// <param name="id">The identifier of the entity currently demanding load.</param>
195251 /// <param name="batchSize">The maximum number of keys to return</param>
196252 /// <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 )
198254 {
199255 object [ ] ids = new object [ batchSize ] ;
200256 ids [ 0 ] = id ; //first element of array is reserved for the actual instance we are loading!
201257 int i = 1 ;
202258 int end = - 1 ;
203259 bool checkForEnd = false ;
204260
205- foreach ( EntityKey key in batchLoadableEntityKeys . Keys )
261+ if ( batchLoadableEntityKeys . TryGetValue ( persister . EntityName , out var set ) )
206262 {
207- if ( key . EntityName . Equals ( persister . EntityName ) )
263+ foreach ( var key in set )
208264 {
209265 //TODO: this needn't exclude subclasses...
210266 if ( checkForEnd && i == end )
0 commit comments