PageRenderTime 61ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NHibernate/Loader/Loader.cs

https://github.com/okb/nhibernate-core
C# | 1761 lines | 1188 code | 219 blank | 354 comment | 195 complexity | ad7244b9a67ca07b9ac136df746ce830 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, MPL-2.0-no-copyleft-exception, LGPL-3.0, Apache-2.0, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using System.Runtime.CompilerServices;
  8. using Iesi.Collections;
  9. using Iesi.Collections.Generic;
  10. using NHibernate.AdoNet;
  11. using NHibernate.Cache;
  12. using NHibernate.Collection;
  13. using NHibernate.Driver;
  14. using NHibernate.Engine;
  15. using NHibernate.Event;
  16. using NHibernate.Exceptions;
  17. using NHibernate.Hql.Classic;
  18. using NHibernate.Hql.Util;
  19. using NHibernate.Impl;
  20. using NHibernate.Param;
  21. using NHibernate.Persister.Collection;
  22. using NHibernate.Persister.Entity;
  23. using NHibernate.Proxy;
  24. using NHibernate.SqlCommand;
  25. using NHibernate.Transform;
  26. using NHibernate.Type;
  27. using NHibernate.Util;
  28. namespace NHibernate.Loader
  29. {
  30. /// <summary>
  31. /// Abstract superclass of object loading (and querying) strategies.
  32. /// </summary>
  33. /// <remarks>
  34. /// <p>
  35. /// This class implements useful common functionality that concrete loaders would delegate to.
  36. /// It is not intended that this functionality would be directly accessed by client code (Hence,
  37. /// all methods of this class are declared <c>protected</c> or <c>private</c>.) This class relies heavily upon the
  38. /// <see cref="ILoadable" /> interface, which is the contract between this class and
  39. /// <see cref="IEntityPersister" />s that may be loaded by it.
  40. /// </p>
  41. /// <p>
  42. /// The present implementation is able to load any number of columns of entities and at most
  43. /// one collection role per query.
  44. /// </p>
  45. /// </remarks>
  46. /// <seealso cref="NHibernate.Persister.Entity.ILoadable"/>
  47. public abstract class Loader
  48. {
  49. private static readonly IInternalLogger Log = LoggerProvider.LoggerFor(typeof(Loader));
  50. private readonly ISessionFactoryImplementor _factory;
  51. private readonly SessionFactoryHelper _helper;
  52. private ColumnNameCache _columnNameCache;
  53. protected Loader(ISessionFactoryImplementor factory)
  54. {
  55. _factory = factory;
  56. _helper = new SessionFactoryHelper(factory);
  57. }
  58. protected SessionFactoryHelper Helper
  59. {
  60. get { return _helper; }
  61. }
  62. /// <summary>
  63. /// An array indicating whether the entities have eager property fetching
  64. /// enabled.
  65. /// </summary>
  66. /// <value> Eager property fetching indicators. </value>
  67. protected virtual bool[] EntityEagerPropertyFetches
  68. {
  69. get { return null; }
  70. }
  71. /// <summary>
  72. /// An array of indexes of the entity that owns a one-to-one association
  73. /// to the entity at the given index (-1 if there is no "owner")
  74. /// </summary>
  75. /// <remarks>
  76. /// The indexes contained here are relative to the result of <see cref="EntityPersisters"/>.
  77. /// </remarks>
  78. protected virtual int[] Owners
  79. {
  80. get { return null; }
  81. }
  82. /// <summary>
  83. /// An array of the owner types corresponding to the <see cref="Owners"/>
  84. /// returns. Indices indicating no owner would be null here.
  85. /// </summary>
  86. protected virtual EntityType[] OwnerAssociationTypes
  87. {
  88. get { return null; }
  89. }
  90. /// <summary>
  91. /// Get the index of the entity that owns the collection, or -1
  92. /// if there is no owner in the query results (i.e. in the case of a
  93. /// collection initializer) or no collection.
  94. /// </summary>
  95. protected virtual int[] CollectionOwners
  96. {
  97. get { return null; }
  98. }
  99. /// <summary>
  100. /// Return false is this loader is a batch entity loader
  101. /// </summary>
  102. protected virtual bool IsSingleRowLoader
  103. {
  104. get { return false; }
  105. }
  106. public virtual bool IsSubselectLoadingEnabled
  107. {
  108. get { return false; }
  109. }
  110. /// <summary>
  111. /// Get the result set descriptor
  112. /// </summary>
  113. protected abstract IEntityAliases[] EntityAliases { get; }
  114. protected abstract ICollectionAliases[] CollectionAliases { get; }
  115. public ISessionFactoryImplementor Factory
  116. {
  117. get { return _factory; }
  118. }
  119. /// <summary>
  120. /// The SqlString to be called; implemented by all subclasses
  121. /// </summary>
  122. /// <remarks>
  123. /// <para>
  124. /// The <c>setter</c> was added so that class inheriting from Loader could write a
  125. /// value using the Property instead of directly to the field.
  126. /// </para>
  127. /// <para>
  128. /// The scope is <c>protected internal</c> because the <see cref="Hql.Classic.WhereParser"/> needs to
  129. /// be able to <c>get</c> the SqlString of the <see cref="Hql.Classic.QueryTranslator"/> when
  130. /// it is parsing a subquery.
  131. /// </para>
  132. /// </remarks>
  133. public abstract SqlString SqlString { get; }
  134. /// <summary>
  135. /// An array of persisters of entity classes contained in each row of results;
  136. /// implemented by all subclasses
  137. /// </summary>
  138. /// <remarks>
  139. /// The <c>setter</c> was added so that classes inheriting from Loader could write a
  140. /// value using the Property instead of directly to the field.
  141. /// </remarks>
  142. public abstract ILoadable[] EntityPersisters { get; }
  143. /// <summary>
  144. /// An (optional) persister for a collection to be initialized; only collection loaders
  145. /// return a non-null value
  146. /// </summary>
  147. protected virtual ICollectionPersister[] CollectionPersisters
  148. {
  149. get { return null; }
  150. }
  151. /// <summary>
  152. /// What lock mode does this load entities with?
  153. /// </summary>
  154. /// <param name="lockModes">A Collection of lock modes specified dynamically via the Query Interface</param>
  155. /// <returns></returns>
  156. public abstract LockMode[] GetLockModes(IDictionary<string, LockMode> lockModes);
  157. /// <summary>
  158. /// Append <c>FOR UPDATE OF</c> clause, if necessary. This
  159. /// empty superclass implementation merely returns its first
  160. /// argument.
  161. /// </summary>
  162. protected virtual SqlString ApplyLocks(SqlString sql, IDictionary<string, LockMode> lockModes, Dialect.Dialect dialect)
  163. {
  164. return sql;
  165. }
  166. /// <summary>
  167. /// Does this query return objects that might be already cached by
  168. /// the session, whose lock mode may need upgrading.
  169. /// </summary>
  170. /// <returns></returns>
  171. protected virtual bool UpgradeLocks()
  172. {
  173. return false;
  174. }
  175. /// <summary>
  176. /// Get the SQL table aliases of entities whose
  177. /// associations are subselect-loadable, returning
  178. /// null if this loader does not support subselect
  179. /// loading
  180. /// </summary>
  181. protected virtual string[] Aliases
  182. {
  183. get { return null; }
  184. }
  185. /// <summary>
  186. /// Modify the SQL, adding lock hints and comments, if necessary
  187. /// </summary>
  188. protected virtual SqlString PreprocessSQL(SqlString sql, QueryParameters parameters, Dialect.Dialect dialect)
  189. {
  190. sql = ApplyLocks(sql, parameters.LockModes, dialect);
  191. return Factory.Settings.IsCommentsEnabled ? PrependComment(sql, parameters) : sql;
  192. }
  193. private static SqlString PrependComment(SqlString sql, QueryParameters parameters)
  194. {
  195. string comment = parameters.Comment;
  196. if (string.IsNullOrEmpty(comment))
  197. {
  198. return sql;
  199. }
  200. else
  201. {
  202. return sql.Insert(0, "/* " + comment + " */");
  203. }
  204. }
  205. /// <summary>
  206. /// Execute an SQL query and attempt to instantiate instances of the class mapped by the given
  207. /// persister from each row of the <c>DataReader</c>. If an object is supplied, will attempt to
  208. /// initialize that object. If a collection is supplied, attempt to initialize that collection.
  209. /// </summary>
  210. private IList DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies)
  211. {
  212. IPersistenceContext persistenceContext = session.PersistenceContext;
  213. bool defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;
  214. if (queryParameters.IsReadOnlyInitialized)
  215. persistenceContext.DefaultReadOnly = queryParameters.ReadOnly;
  216. else
  217. queryParameters.ReadOnly = persistenceContext.DefaultReadOnly;
  218. persistenceContext.BeforeLoad();
  219. IList result;
  220. try
  221. {
  222. try
  223. {
  224. result = DoQuery(session, queryParameters, returnProxies);
  225. }
  226. finally
  227. {
  228. persistenceContext.AfterLoad();
  229. }
  230. persistenceContext.InitializeNonLazyCollections();
  231. }
  232. finally
  233. {
  234. persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
  235. }
  236. return result;
  237. }
  238. /// <summary>
  239. /// Loads a single row from the result set. This is the processing used from the
  240. /// ScrollableResults where no collection fetches were encountered.
  241. /// </summary>
  242. /// <param name="resultSet">The result set from which to do the load.</param>
  243. /// <param name="session">The session from which the request originated.</param>
  244. /// <param name="queryParameters">The query parameters specified by the user.</param>
  245. /// <param name="returnProxies">Should proxies be generated</param>
  246. /// <returns>The loaded "row".</returns>
  247. /// <exception cref="HibernateException" />
  248. protected object LoadSingleRow(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters,
  249. bool returnProxies)
  250. {
  251. int entitySpan = EntityPersisters.Length;
  252. IList hydratedObjects = entitySpan == 0 ? null : new List<object>(entitySpan);
  253. object result;
  254. try
  255. {
  256. result =
  257. GetRowFromResultSet(resultSet, session, queryParameters, GetLockModes(queryParameters.LockModes), null,
  258. hydratedObjects, new EntityKey[entitySpan], returnProxies);
  259. }
  260. catch (HibernateException)
  261. {
  262. throw; // Don't call Convert on HibernateExceptions
  263. }
  264. catch (Exception sqle)
  265. {
  266. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle, "could not read next row of results",
  267. SqlString, queryParameters.PositionalParameterValues,
  268. queryParameters.NamedParameters);
  269. }
  270. InitializeEntitiesAndCollections(hydratedObjects, resultSet, session, queryParameters.IsReadOnly(session));
  271. session.PersistenceContext.InitializeNonLazyCollections();
  272. return result;
  273. }
  274. // Not ported: sequentialLoad, loadSequentialRowsForward, loadSequentialRowsReverse
  275. internal static EntityKey GetOptionalObjectKey(QueryParameters queryParameters, ISessionImplementor session)
  276. {
  277. object optionalObject = queryParameters.OptionalObject;
  278. object optionalId = queryParameters.OptionalId;
  279. string optionalEntityName = queryParameters.OptionalEntityName;
  280. if (optionalObject != null && !string.IsNullOrEmpty(optionalEntityName))
  281. {
  282. return new EntityKey(optionalId, session.GetEntityPersister(optionalEntityName, optionalObject), session.EntityMode);
  283. }
  284. else
  285. {
  286. return null;
  287. }
  288. }
  289. internal object GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session,
  290. QueryParameters queryParameters, LockMode[] lockModeArray,
  291. EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys,
  292. bool returnProxies)
  293. {
  294. ILoadable[] persisters = EntityPersisters;
  295. int entitySpan = persisters.Length;
  296. for (int i = 0; i < entitySpan; i++)
  297. {
  298. keys[i] =
  299. GetKeyFromResultSet(i, persisters[i], i == entitySpan - 1 ? queryParameters.OptionalId : null, resultSet, session);
  300. //TODO: the i==entitySpan-1 bit depends upon subclass implementation (very bad)
  301. }
  302. RegisterNonExists(keys, session);
  303. // this call is side-effecty
  304. object[] row =
  305. GetRow(resultSet, persisters, keys, queryParameters.OptionalObject, optionalObjectKey, lockModeArray,
  306. hydratedObjects, session);
  307. ReadCollectionElements(row, resultSet, session);
  308. if (returnProxies)
  309. {
  310. // now get an existing proxy for each row element (if there is one)
  311. for (int i = 0; i < entitySpan; i++)
  312. {
  313. object entity = row[i];
  314. object proxy = session.PersistenceContext.ProxyFor(persisters[i], keys[i], entity);
  315. if (entity != proxy)
  316. {
  317. // Force the proxy to resolve itself
  318. ((INHibernateProxy)proxy).HibernateLazyInitializer.SetImplementation(entity);
  319. row[i] = proxy;
  320. }
  321. }
  322. }
  323. return GetResultColumnOrRow(row, queryParameters.ResultTransformer, resultSet, session);
  324. }
  325. /// <summary>
  326. /// Read any collection elements contained in a single row of the result set
  327. /// </summary>
  328. private void ReadCollectionElements(object[] row, IDataReader resultSet, ISessionImplementor session)
  329. {
  330. //TODO: make this handle multiple collection roles!
  331. ICollectionPersister[] collectionPersisters = CollectionPersisters;
  332. if (collectionPersisters != null)
  333. {
  334. ICollectionAliases[] descriptors = CollectionAliases;
  335. int[] collectionOwners = CollectionOwners;
  336. for (int i = 0; i < collectionPersisters.Length; i++)
  337. {
  338. bool hasCollectionOwners = collectionOwners != null && collectionOwners[i] > -1;
  339. //true if this is a query and we are loading multiple instances of the same collection role
  340. //otherwise this is a CollectionInitializer and we are loading up a single collection or batch
  341. object owner = hasCollectionOwners ? row[collectionOwners[i]] : null;
  342. //if null, owner will be retrieved from session
  343. ICollectionPersister collectionPersister = collectionPersisters[i];
  344. object key;
  345. if (owner == null)
  346. {
  347. key = null;
  348. }
  349. else
  350. {
  351. key = collectionPersister.CollectionType.GetKeyOfOwner(owner, session);
  352. //TODO: old version did not require hashmap lookup:
  353. //keys[collectionOwner].getIdentifier()
  354. }
  355. ReadCollectionElement(owner, key, collectionPersister, descriptors[i], resultSet, session);
  356. }
  357. }
  358. }
  359. private IList DoQuery(ISessionImplementor session, QueryParameters queryParameters, bool returnProxies)
  360. {
  361. RowSelection selection = queryParameters.RowSelection;
  362. int maxRows = HasMaxRows(selection) ? selection.MaxRows : int.MaxValue;
  363. int entitySpan = EntityPersisters.Length;
  364. List<object> hydratedObjects = entitySpan == 0 ? null : new List<object>(entitySpan * 10);
  365. IDbCommand st = PrepareQueryCommand(queryParameters, false, session);
  366. IDataReader rs = GetResultSet(st, queryParameters.HasAutoDiscoverScalarTypes, queryParameters.Callable, selection,
  367. session);
  368. // would be great to move all this below here into another method that could also be used
  369. // from the new scrolling stuff.
  370. //
  371. // Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
  372. // that I could do the control breaking at the means to know when to stop
  373. LockMode[] lockModeArray = GetLockModes(queryParameters.LockModes);
  374. EntityKey optionalObjectKey = GetOptionalObjectKey(queryParameters, session);
  375. bool createSubselects = IsSubselectLoadingEnabled;
  376. List<EntityKey[]> subselectResultKeys = createSubselects ? new List<EntityKey[]>() : null;
  377. IList results = new List<object>();
  378. try
  379. {
  380. HandleEmptyCollections(queryParameters.CollectionKeys, rs, session);
  381. EntityKey[] keys = new EntityKey[entitySpan]; // we can reuse it each time
  382. if (Log.IsDebugEnabled)
  383. {
  384. Log.Debug("processing result set");
  385. }
  386. int count;
  387. for (count = 0; count < maxRows && rs.Read(); count++)
  388. {
  389. if (Log.IsDebugEnabled)
  390. {
  391. Log.Debug("result set row: " + count);
  392. }
  393. object result = GetRowFromResultSet(rs, session, queryParameters, lockModeArray, optionalObjectKey, hydratedObjects,
  394. keys, returnProxies);
  395. results.Add(result);
  396. if (createSubselects)
  397. {
  398. subselectResultKeys.Add(keys);
  399. keys = new EntityKey[entitySpan]; //can't reuse in this case
  400. }
  401. }
  402. if (Log.IsDebugEnabled)
  403. {
  404. Log.Debug(string.Format("done processing result set ({0} rows)", count));
  405. }
  406. }
  407. catch (Exception e)
  408. {
  409. e.Data["actual-sql-query"] = st.CommandText;
  410. throw;
  411. }
  412. finally
  413. {
  414. session.Batcher.CloseCommand(st, rs);
  415. }
  416. InitializeEntitiesAndCollections(hydratedObjects, rs, session, queryParameters.IsReadOnly(session));
  417. if (createSubselects)
  418. {
  419. CreateSubselects(subselectResultKeys, queryParameters, session);
  420. }
  421. return results;
  422. }
  423. protected bool HasSubselectLoadableCollections()
  424. {
  425. foreach (ILoadable loadable in EntityPersisters)
  426. {
  427. if (loadable.HasSubselectLoadableCollections)
  428. {
  429. return true;
  430. }
  431. }
  432. return false;
  433. }
  434. private static ISet<EntityKey>[] Transpose(IList<EntityKey[]> keys)
  435. {
  436. ISet<EntityKey>[] result = new ISet<EntityKey>[keys[0].Length];
  437. for (int j = 0; j < result.Length; j++)
  438. {
  439. result[j] = new HashedSet<EntityKey>();
  440. for (int i = 0; i < keys.Count; i++)
  441. {
  442. EntityKey key = keys[i][j];
  443. if (key != null)
  444. {
  445. result[j].Add(key);
  446. }
  447. }
  448. }
  449. return result;
  450. }
  451. internal void CreateSubselects(IList<EntityKey[]> keys, QueryParameters queryParameters, ISessionImplementor session)
  452. {
  453. if (keys.Count > 1)
  454. {
  455. //if we only returned one entity, query by key is more efficient
  456. var subSelects = CreateSubselects(keys, queryParameters).ToArray();
  457. foreach (EntityKey[] rowKeys in keys)
  458. {
  459. for (int i = 0; i < rowKeys.Length; i++)
  460. {
  461. if (rowKeys[i] != null && subSelects[i] != null)
  462. {
  463. session.PersistenceContext.BatchFetchQueue.AddSubselect(rowKeys[i], subSelects[i]);
  464. }
  465. }
  466. }
  467. }
  468. }
  469. private IEnumerable<SubselectFetch> CreateSubselects(IList<EntityKey[]> keys, QueryParameters queryParameters)
  470. {
  471. // see NH-2123 NH-2125
  472. ISet<EntityKey>[] keySets = Transpose(keys);
  473. ILoadable[] loadables = EntityPersisters;
  474. string[] aliases = Aliases;
  475. for (int i = 0; i < loadables.Length; i++)
  476. {
  477. if (loadables[i].HasSubselectLoadableCollections)
  478. {
  479. yield return new SubselectFetch(aliases[i], loadables[i], queryParameters, keySets[i]);
  480. }
  481. else
  482. {
  483. yield return null;
  484. }
  485. }
  486. }
  487. internal void InitializeEntitiesAndCollections(IList hydratedObjects, object resultSetId, ISessionImplementor session, bool readOnly)
  488. {
  489. ICollectionPersister[] collectionPersisters = CollectionPersisters;
  490. if (collectionPersisters != null)
  491. {
  492. for (int i = 0; i < collectionPersisters.Length; i++)
  493. {
  494. if (collectionPersisters[i].IsArray)
  495. {
  496. //for arrays, we should end the collection load before resolving
  497. //the entities, since the actual array instances are not instantiated
  498. //during loading
  499. //TODO: or we could do this polymorphically, and have two
  500. // different operations implemented differently for arrays
  501. EndCollectionLoad(resultSetId, session, collectionPersisters[i]);
  502. }
  503. }
  504. }
  505. //important: reuse the same event instances for performance!
  506. PreLoadEvent pre;
  507. PostLoadEvent post;
  508. if (session.IsEventSource)
  509. {
  510. var eventSourceSession = (IEventSource)session;
  511. pre = new PreLoadEvent(eventSourceSession);
  512. post = new PostLoadEvent(eventSourceSession);
  513. }
  514. else
  515. {
  516. pre = null;
  517. post = null;
  518. }
  519. if (hydratedObjects != null)
  520. {
  521. int hydratedObjectsSize = hydratedObjects.Count;
  522. if (Log.IsDebugEnabled)
  523. {
  524. Log.Debug(string.Format("total objects hydrated: {0}", hydratedObjectsSize));
  525. }
  526. for (int i = 0; i < hydratedObjectsSize; i++)
  527. {
  528. TwoPhaseLoad.InitializeEntity(hydratedObjects[i], readOnly, session, pre, post);
  529. }
  530. }
  531. if (collectionPersisters != null)
  532. {
  533. for (int i = 0; i < collectionPersisters.Length; i++)
  534. {
  535. if (!collectionPersisters[i].IsArray)
  536. {
  537. //for sets, we should end the collection load after resolving
  538. //the entities, since we might call hashCode() on the elements
  539. //TODO: or we could do this polymorphically, and have two
  540. // different operations implemented differently for arrays
  541. EndCollectionLoad(resultSetId, session, collectionPersisters[i]);
  542. }
  543. }
  544. }
  545. }
  546. private static void EndCollectionLoad(object resultSetId, ISessionImplementor session, ICollectionPersister collectionPersister)
  547. {
  548. //this is a query and we are loading multiple instances of the same collection role
  549. session.PersistenceContext.LoadContexts.GetCollectionLoadContext((IDataReader)resultSetId).EndLoadingCollections(
  550. collectionPersister);
  551. }
  552. public virtual IList GetResultList(IList results, IResultTransformer resultTransformer)
  553. {
  554. return results;
  555. }
  556. /// <summary>
  557. /// Get the actual object that is returned in the user-visible result list.
  558. /// </summary>
  559. /// <remarks>
  560. /// This empty implementation merely returns its first argument. This is
  561. /// overridden by some subclasses.
  562. /// </remarks>
  563. protected virtual object GetResultColumnOrRow(object[] row, IResultTransformer resultTransformer, IDataReader rs, ISessionImplementor session)
  564. {
  565. return row;
  566. }
  567. /// <summary>
  568. /// For missing objects associated by one-to-one with another object in the
  569. /// result set, register the fact that the the object is missing with the
  570. /// session.
  571. /// </summary>
  572. private void RegisterNonExists(EntityKey[] keys, ISessionImplementor session)
  573. {
  574. int[] owners = Owners;
  575. if (owners != null)
  576. {
  577. EntityType[] ownerAssociationTypes = OwnerAssociationTypes;
  578. for (int i = 0; i < keys.Length; i++)
  579. {
  580. int owner = owners[i];
  581. if (owner > -1)
  582. {
  583. EntityKey ownerKey = keys[owner];
  584. if (keys[i] == null && ownerKey != null)
  585. {
  586. bool isOneToOneAssociation = ownerAssociationTypes != null && ownerAssociationTypes[i] != null
  587. && ownerAssociationTypes[i].IsOneToOne;
  588. if (isOneToOneAssociation)
  589. {
  590. session.PersistenceContext.AddNullProperty(ownerKey, ownerAssociationTypes[i].PropertyName);
  591. }
  592. }
  593. }
  594. }
  595. }
  596. }
  597. /// <summary>
  598. /// Read one collection element from the current row of the ADO.NET result set
  599. /// </summary>
  600. private static void ReadCollectionElement(object optionalOwner, object optionalKey, ICollectionPersister persister,
  601. ICollectionAliases descriptor, IDataReader rs, ISessionImplementor session)
  602. {
  603. IPersistenceContext persistenceContext = session.PersistenceContext;
  604. object collectionRowKey = persister.ReadKey(rs, descriptor.SuffixedKeyAliases, session);
  605. if (collectionRowKey != null)
  606. {
  607. // we found a collection element in the result set
  608. if (Log.IsDebugEnabled)
  609. {
  610. Log.Debug("found row of collection: " + MessageHelper.InfoString(persister, collectionRowKey));
  611. }
  612. object owner = optionalOwner;
  613. if (owner == null)
  614. {
  615. owner = persistenceContext.GetCollectionOwner(collectionRowKey, persister);
  616. if (owner == null)
  617. {
  618. //TODO: This is assertion is disabled because there is a bug that means the
  619. // original owner of a transient, uninitialized collection is not known
  620. // if the collection is re-referenced by a different object associated
  621. // with the current Session
  622. //throw new AssertionFailure("bug loading unowned collection");
  623. }
  624. }
  625. IPersistentCollection rowCollection =
  626. persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, collectionRowKey);
  627. if (rowCollection != null)
  628. {
  629. rowCollection.ReadFrom(rs, persister, descriptor, owner);
  630. }
  631. }
  632. else if (optionalKey != null)
  633. {
  634. // we did not find a collection element in the result set, so we
  635. // ensure that a collection is created with the owner's identifier,
  636. // since what we have is an empty collection
  637. if (Log.IsDebugEnabled)
  638. {
  639. Log.Debug("result set contains (possibly empty) collection: " + MessageHelper.InfoString(persister, optionalKey));
  640. }
  641. persistenceContext.LoadContexts.GetCollectionLoadContext(rs).GetLoadingCollection(persister, optionalKey);
  642. // handle empty collection
  643. }
  644. // else no collection element, but also no owner
  645. }
  646. /// <summary>
  647. /// If this is a collection initializer, we need to tell the session that a collection
  648. /// is being initilized, to account for the possibility of the collection having
  649. /// no elements (hence no rows in the result set).
  650. /// </summary>
  651. internal void HandleEmptyCollections(object[] keys, object resultSetId, ISessionImplementor session)
  652. {
  653. if (keys != null)
  654. {
  655. // this is a collection initializer, so we must create a collection
  656. // for each of the passed-in keys, to account for the possibility
  657. // that the collection is empty and has no rows in the result set
  658. ICollectionPersister[] collectionPersisters = CollectionPersisters;
  659. for (int j = 0; j < collectionPersisters.Length; j++)
  660. {
  661. for (int i = 0; i < keys.Length; i++)
  662. {
  663. // handle empty collections
  664. if (Log.IsDebugEnabled)
  665. {
  666. Log.Debug("result set contains (possibly empty) collection: "
  667. + MessageHelper.InfoString(collectionPersisters[j], keys[i]));
  668. }
  669. session.PersistenceContext.LoadContexts.GetCollectionLoadContext((IDataReader)resultSetId).GetLoadingCollection(
  670. collectionPersisters[j], keys[i]);
  671. }
  672. }
  673. }
  674. // else this is not a collection initializer (and empty collections will
  675. // be detected by looking for the owner's identifier in the result set)
  676. }
  677. /// <summary>
  678. /// Read a row of <c>EntityKey</c>s from the <c>IDataReader</c> into the given array.
  679. /// </summary>
  680. /// <remarks>
  681. /// Warning: this method is side-effecty. If an <c>id</c> is given, don't bother going
  682. /// to the <c>IDataReader</c>
  683. /// </remarks>
  684. private EntityKey GetKeyFromResultSet(int i, IEntityPersister persister, object id, IDataReader rs, ISessionImplementor session)
  685. {
  686. object resultId;
  687. // if we know there is exactly 1 row, we can skip.
  688. // it would be great if we could _always_ skip this;
  689. // it is a problem for <key-many-to-one>
  690. if (IsSingleRowLoader && id != null)
  691. {
  692. resultId = id;
  693. }
  694. else
  695. {
  696. IType idType = persister.IdentifierType;
  697. resultId = idType.NullSafeGet(rs, EntityAliases[i].SuffixedKeyAliases, session, null);
  698. bool idIsResultId = id != null && resultId != null && idType.IsEqual(id, resultId, session.EntityMode, _factory);
  699. if (idIsResultId)
  700. {
  701. resultId = id; //use the id passed in
  702. }
  703. }
  704. return resultId == null ? null : new EntityKey(resultId, persister, session.EntityMode);
  705. }
  706. /// <summary>
  707. /// Check the version of the object in the <c>IDataReader</c> against
  708. /// the object version in the session cache, throwing an exception
  709. /// if the version numbers are different.
  710. /// </summary>
  711. /// <exception cref="StaleObjectStateException"></exception>
  712. private void CheckVersion(int i, IEntityPersister persister, object id, object entity, IDataReader rs, ISessionImplementor session)
  713. {
  714. object version = session.PersistenceContext.GetEntry(entity).Version;
  715. // null version means the object is in the process of being loaded somewhere else in the ResultSet
  716. if (version != null)
  717. {
  718. IVersionType versionType = persister.VersionType;
  719. object currentVersion = versionType.NullSafeGet(rs, EntityAliases[i].SuffixedVersionAliases, session, null);
  720. if (!versionType.IsEqual(version, currentVersion))
  721. {
  722. if (session.Factory.Statistics.IsStatisticsEnabled)
  723. {
  724. session.Factory.StatisticsImplementor.OptimisticFailure(persister.EntityName);
  725. }
  726. throw new StaleObjectStateException(persister.EntityName, id);
  727. }
  728. }
  729. }
  730. /// <summary>
  731. /// Resolve any ids for currently loaded objects, duplications within the <c>IDataReader</c>,
  732. /// etc. Instanciate empty objects to be initialized from the <c>IDataReader</c>. Return an
  733. /// array of objects (a row of results) and an array of booleans (by side-effect) that determine
  734. /// wheter the corresponding object should be initialized
  735. /// </summary>
  736. private object[] GetRow(IDataReader rs, ILoadable[] persisters, EntityKey[] keys, object optionalObject,
  737. EntityKey optionalObjectKey, LockMode[] lockModes, IList hydratedObjects,
  738. ISessionImplementor session)
  739. {
  740. int cols = persisters.Length;
  741. IEntityAliases[] descriptors = EntityAliases;
  742. if (Log.IsDebugEnabled)
  743. {
  744. Log.Debug("result row: " + StringHelper.ToString(keys));
  745. }
  746. object[] rowResults = new object[cols];
  747. for (int i = 0; i < cols; i++)
  748. {
  749. object obj = null;
  750. EntityKey key = keys[i];
  751. if (keys[i] == null)
  752. {
  753. // do nothing
  754. /* TODO NH-1001 : if (persisters[i]...EntityType) is an OneToMany or a ManyToOne and
  755. * the keys.length > 1 and the relation IsIgnoreNotFound probably we are in presence of
  756. * an load with "outer join" the relation can be considerer loaded even if the key is null (mean not found)
  757. */
  758. }
  759. else
  760. {
  761. //If the object is already loaded, return the loaded one
  762. obj = session.GetEntityUsingInterceptor(key);
  763. if (obj != null)
  764. {
  765. //its already loaded so dont need to hydrate it
  766. InstanceAlreadyLoaded(rs, i, persisters[i], key, obj, lockModes[i], session);
  767. }
  768. else
  769. {
  770. obj =
  771. InstanceNotYetLoaded(rs, i, persisters[i], key, lockModes[i], descriptors[i].RowIdAlias, optionalObjectKey,
  772. optionalObject, hydratedObjects, session);
  773. }
  774. }
  775. rowResults[i] = obj;
  776. }
  777. return rowResults;
  778. }
  779. /// <summary>
  780. /// The entity instance is already in the session cache
  781. /// </summary>
  782. private void InstanceAlreadyLoaded(IDataReader rs, int i, IEntityPersister persister, EntityKey key, object obj,
  783. LockMode lockMode, ISessionImplementor session)
  784. {
  785. if (!persister.IsInstance(obj, session.EntityMode))
  786. {
  787. string errorMsg = string.Format("loading object was of wrong class [{0}]", obj.GetType().FullName);
  788. throw new WrongClassException(errorMsg, key.Identifier, persister.EntityName);
  789. }
  790. if (LockMode.None != lockMode && UpgradeLocks())
  791. {
  792. EntityEntry entry = session.PersistenceContext.GetEntry(obj);
  793. bool isVersionCheckNeeded = persister.IsVersioned && entry.LockMode.LessThan(lockMode);
  794. // we don't need to worry about existing version being uninitialized
  795. // because this block isn't called by a re-entrant load (re-entrant
  796. // load _always_ have lock mode NONE
  797. if (isVersionCheckNeeded)
  798. {
  799. // we only check the version when _upgrading_ lock modes
  800. CheckVersion(i, persister, key.Identifier, obj, rs, session);
  801. // we need to upgrade the lock mode to the mode requested
  802. entry.LockMode = lockMode;
  803. }
  804. }
  805. }
  806. /// <summary>
  807. /// The entity instance is not in the session cache
  808. /// </summary>
  809. private object InstanceNotYetLoaded(IDataReader dr, int i, ILoadable persister, EntityKey key, LockMode lockMode,
  810. string rowIdAlias, EntityKey optionalObjectKey, object optionalObject,
  811. IList hydratedObjects, ISessionImplementor session)
  812. {
  813. object obj;
  814. string instanceClass = GetInstanceClass(dr, i, persister, key.Identifier, session);
  815. if (optionalObjectKey != null && key.Equals(optionalObjectKey))
  816. {
  817. // its the given optional object
  818. obj = optionalObject;
  819. }
  820. else
  821. {
  822. obj = session.Instantiate(instanceClass, key.Identifier);
  823. }
  824. // need to hydrate it
  825. // grab its state from the DataReader and keep it in the Session
  826. // (but don't yet initialize the object itself)
  827. // note that we acquired LockMode.READ even if it was not requested
  828. LockMode acquiredLockMode = lockMode == LockMode.None ? LockMode.Read : lockMode;
  829. LoadFromResultSet(dr, i, obj, instanceClass, key, rowIdAlias, acquiredLockMode, persister, session);
  830. // materialize associations (and initialize the object) later
  831. hydratedObjects.Add(obj);
  832. return obj;
  833. }
  834. private bool IsEagerPropertyFetchEnabled(int i)
  835. {
  836. bool[] array = EntityEagerPropertyFetches;
  837. return array != null && array[i];
  838. }
  839. /// <summary>
  840. /// Hydrate the state of an object from the SQL <c>IDataReader</c>, into
  841. /// an array of "hydrated" values (do not resolve associations yet),
  842. /// and pass the hydrated state to the session.
  843. /// </summary>
  844. private void LoadFromResultSet(IDataReader rs, int i, object obj, string instanceClass, EntityKey key,
  845. string rowIdAlias, LockMode lockMode, ILoadable rootPersister,
  846. ISessionImplementor session)
  847. {
  848. object id = key.Identifier;
  849. // Get the persister for the _subclass_
  850. ILoadable persister = (ILoadable)Factory.GetEntityPersister(instanceClass);
  851. if (Log.IsDebugEnabled)
  852. {
  853. Log.Debug("Initializing object from DataReader: " + MessageHelper.InfoString(persister, id));
  854. }
  855. bool eagerPropertyFetch = IsEagerPropertyFetchEnabled(i);
  856. // add temp entry so that the next step is circular-reference
  857. // safe - only needed because some types don't take proper
  858. // advantage of two-phase-load (esp. components)
  859. TwoPhaseLoad.AddUninitializedEntity(key, obj, persister, lockMode, !eagerPropertyFetch, session);
  860. // This is not very nice (and quite slow):
  861. string[][] cols = persister == rootPersister
  862. ? EntityAliases[i].SuffixedPropertyAliases
  863. : EntityAliases[i].GetSuffixedPropertyAliases(persister);
  864. object[] values = persister.Hydrate(rs, id, obj, rootPersister, cols, eagerPropertyFetch, session);
  865. object rowId = persister.HasRowId ? rs[rowIdAlias] : null;
  866. IAssociationType[] ownerAssociationTypes = OwnerAssociationTypes;
  867. if (ownerAssociationTypes != null && ownerAssociationTypes[i] != null)
  868. {
  869. string ukName = ownerAssociationTypes[i].RHSUniqueKeyPropertyName;
  870. if (ukName != null)
  871. {
  872. int index = ((IUniqueKeyLoadable)persister).GetPropertyIndex(ukName);
  873. IType type = persister.PropertyTypes[index];
  874. // polymorphism not really handled completely correctly,
  875. // perhaps...well, actually its ok, assuming that the
  876. // entity name used in the lookup is the same as the
  877. // the one used here, which it will be
  878. EntityUniqueKey euk =
  879. new EntityUniqueKey(rootPersister.EntityName, ukName, type.SemiResolve(values[index], session, obj), type,
  880. session.EntityMode, session.Factory);
  881. session.PersistenceContext.AddEntity(euk, obj);
  882. }
  883. }
  884. TwoPhaseLoad.PostHydrate(persister, id, values, rowId, obj, lockMode, !eagerPropertyFetch, session);
  885. }
  886. /// <summary>
  887. /// Determine the concrete class of an instance for the <c>IDataReader</c>
  888. /// </summary>
  889. private string GetInstanceClass(IDataReader rs, int i, ILoadable persister, object id, ISessionImplementor session)
  890. {
  891. if (persister.HasSubclasses)
  892. {
  893. // code to handle subclasses of topClass
  894. object discriminatorValue =
  895. persister.DiscriminatorType.NullSafeGet(rs, EntityAliases[i].SuffixedDiscriminatorAlias, session, null);
  896. string result = persister.GetSubclassForDiscriminatorValue(discriminatorValue);
  897. if (result == null)
  898. {
  899. // woops we got an instance of another class hierarchy branch.
  900. throw new WrongClassException(string.Format("Discriminator was: '{0}'", discriminatorValue), id,
  901. persister.EntityName);
  902. }
  903. return result;
  904. }
  905. return persister.EntityName;
  906. }
  907. /// <summary>
  908. /// Advance the cursor to the first required row of the <c>IDataReader</c>
  909. /// </summary>
  910. internal static void Advance(IDataReader rs, RowSelection selection)
  911. {
  912. int firstRow = GetFirstRow(selection);
  913. if (firstRow != 0)
  914. {
  915. // DataReaders are forward-only, readonly, so we have to step through
  916. for (int i = 0; i < firstRow; i++)
  917. {
  918. rs.Read();
  919. }
  920. }
  921. }
  922. internal static bool HasMaxRows(RowSelection selection)
  923. {
  924. // it used to be selection.MaxRows != null -> since an Int32 will always
  925. // have a value I'll compare it to the static field NoValue used to initialize
  926. // max rows to nothing
  927. return selection != null && selection.MaxRows != RowSelection.NoValue;
  928. }
  929. private static bool HasOffset(RowSelection selection)
  930. {
  931. return selection != null && selection.FirstRow != RowSelection.NoValue;
  932. }
  933. internal static int GetFirstRow(RowSelection selection)
  934. {
  935. if (selection == null || !selection.DefinesLimits)
  936. {
  937. return 0;
  938. }
  939. return selection.FirstRow > 0 ? selection.FirstRow : 0;
  940. }
  941. /// <summary>
  942. /// Should we pre-process the SQL string, adding a dialect-specific
  943. /// LIMIT clause.
  944. /// </summary>
  945. /// <param name="selection"></param>
  946. /// <param name="dialect"></param>
  947. /// <returns></returns>
  948. internal bool UseLimit(RowSelection selection, Dialect.Dialect dialect)
  949. {
  950. return dialect.SupportsLimit && (HasMaxRows(selection) || HasOffset(selection));
  951. }
  952. /// <summary>
  953. /// Performs dialect-specific manipulations on the offset value before returning it.
  954. /// This method is applicable for use in limit statements only.
  955. /// </summary>
  956. internal static int? GetOffsetUsingDialect(RowSelection selection, Dialect.Dialect dialect)
  957. {
  958. int firstRow = GetFirstRow(selection);
  959. if (firstRow == 0)
  960. return null;
  961. return dialect.GetOffsetValue(firstRow);
  962. }
  963. /// <summary>
  964. /// Performs dialect-specific manipulations on the limit value before returning it.
  965. /// This method is applicable for use in limit statements only.
  966. /// </summary>
  967. internal static int? GetLimitUsingDialect(RowSelection selection, Dialect.Dialect dialect)
  968. {
  969. if (selection == null || selection.MaxRows == RowSelection.NoValue)
  970. return null;
  971. return dialect.GetLimitValue(GetFirstRow(selection), selection.MaxRows);
  972. }
  973. /// <summary>
  974. /// Obtain an <c>IDbCommand</c> with all parameters pre-bound. Bind positional parameters,
  975. /// named parameters, and limit parameters.
  976. /// </summary>
  977. /// <remarks>
  978. /// Creates an IDbCommand object and populates it with the values necessary to execute it against the
  979. /// database to Load an Entity.
  980. /// </remarks>
  981. /// <param name="queryParameters">The <see cref="QueryParameters"/> to use for the IDbCommand.</param>
  982. /// <param name="scroll">TODO: find out where this is used...</param>
  983. /// <param name="session">The SessionImpl this Command is being prepared in.</param>
  984. /// <returns>A CommandWrapper wrapping an IDbCommand that is ready to be executed.</returns>
  985. protected internal virtual IDbCommand PrepareQueryCommand(QueryParameters queryParameters, bool scroll, ISessionImplementor session)
  986. {
  987. ISqlCommand sqlCommand = CreateSqlCommand(queryParameters, session);
  988. SqlString sqlString = sqlCommand.Query;
  989. sqlCommand.ResetParametersIndexesForTheCommand(0);
  990. IDbCommand command = session.Batcher.PrepareQueryCommand(CommandType.Text, sqlString, sqlCommand.ParameterTypes);
  991. try
  992. {
  993. RowSelection selection = queryParameters.RowSelection;
  994. if (selection != null && selection.Timeout != RowSelection.NoValue)
  995. {
  996. command.CommandTimeout = selection.Timeout;
  997. }
  998. sqlCommand.Bind(command, session);
  999. IDriver driver = _factory.ConnectionProvider.Driver;
  1000. driver.RemoveUnusedCommandParameters(command, sqlString);
  1001. driver.ExpandQueryParameters(command, sqlString);
  1002. }
  1003. catch (HibernateException)
  1004. {
  1005. session.Batcher.CloseCommand(command, null);
  1006. throw;
  1007. }
  1008. catch (Exception sqle)
  1009. {
  1010. session.Batcher.CloseCommand(command, null);
  1011. ADOExceptionReporter.LogExceptions(sqle);
  1012. throw;
  1013. }
  1014. return command;
  1015. }
  1016. /// <summary>
  1017. /// Some dialect-specific LIMIT clauses require the maximium last row number
  1018. /// (aka, first_row_number + total_row_count), while others require the maximum
  1019. /// returned row count (the total maximum number of rows to return).
  1020. /// </summary>
  1021. /// <param name="selection">The selection criteria </param>
  1022. /// <param name="dialect">The dialect </param>
  1023. /// <returns> The appropriate value to bind into the limit clause. </returns>
  1024. internal static int GetMaxOrLimit(Dialect.Dialect dialect, RowSelection selection)
  1025. {
  1026. int firstRow = GetFirstRow(selection);
  1027. int rowCount = selection.MaxRows;
  1028. if (rowCount == RowSelection.NoValue)
  1029. return int.MaxValue;
  1030. return dialect.GetLimitValue(firstRow, rowCount);
  1031. }
  1032. /// <summary>
  1033. /// Fetch a <c>IDbCommand</c>, call <c>SetMaxRows</c> and then execute it,
  1034. /// advance to the first result and return an SQL <c>IDataReader</c>
  1035. /// </summary>
  1036. /// <param name="st">The <see cref="IDbCommand" /> to execute.</param>
  1037. /// <param name="selection">The <see cref="RowSelection"/> to apply to the <see cref="IDbCommand"/> and <see cref="IDataReader"/>.</param>
  1038. /// <param name="autoDiscoverTypes">true if result types need to be auto-discovered by the loader; false otherwise.</param>
  1039. /// <param name="session">The <see cref="ISession" /> to load in.</param>
  1040. /// <param name="callable"></param>
  1041. /// <returns>An IDataReader advanced to the first record in RowSelection.</returns>
  1042. protected IDataReader GetResultSet(IDbCommand st, bool autoDiscoverTypes, bool callable, RowSelection selection, ISessionImplementor session)
  1043. {
  1044. IDataReader rs = null;
  1045. try
  1046. {
  1047. Log.Info(st.CommandText);
  1048. // TODO NH: Callable
  1049. rs = session.Batcher.ExecuteReader(st);
  1050. //NH: this is checked outside the WrapResultSet because we
  1051. // want to avoid the syncronization overhead in the vast majority
  1052. // of cases where IsWrapResultSetsEnabled is set to false
  1053. if (session.Factory.Settings.IsWrapResultSetsEnabled)
  1054. rs = WrapResultSet(rs);
  1055. Dialect.Dialect dialect = session.Factory.Dialect;
  1056. if (!dialect.SupportsLimitOffset || !UseLimit(selection, dialect))
  1057. {
  1058. Advance(rs, selection);
  1059. }
  1060. if (autoDiscoverTypes)
  1061. {
  1062. AutoDiscoverTypes(rs);
  1063. }
  1064. return rs;
  1065. }
  1066. catch (Exception sqle)
  1067. {
  1068. ADOExceptionReporter.LogExceptions(sqle);
  1069. session.Batcher.CloseCommand(st, rs);
  1070. throw;
  1071. }
  1072. }
  1073. protected virtual void AutoDiscoverTypes(IDataReader rs)
  1074. {
  1075. throw new AssertionFailure("Auto discover types not supported in this loader");
  1076. }
  1077. [MethodImpl(MethodImplOptions.Synchronized)]
  1078. private IDataReader WrapResultSet(IDataReader rs)
  1079. {
  1080. // synchronized to avoid multi-thread access issues; defined as method synch to avoid
  1081. // potential deadlock issues due to nature of code.
  1082. try
  1083. {
  1084. Log.Debug("Wrapping result set [" + rs + "]");
  1085. return new ResultSetWrapper(rs, RetreiveColumnNameToIndexCache(rs));
  1086. }
  1087. catch (Exception e)
  1088. {
  1089. Log.Info("Error wrapping result set", e);
  1090. return rs;
  1091. }
  1092. }
  1093. private ColumnNameCache RetreiveColumnNameToIndexCache(IDataReader rs)
  1094. {
  1095. if (_columnNameCache == null)
  1096. {
  1097. Log.Debug("Building columnName->columnIndex cache");
  1098. _columnNameCache = new ColumnNameCache(rs.GetSchemaTable().Rows.Count);
  1099. }
  1100. return _columnNameCache;
  1101. }
  1102. /// <summary>
  1103. /// Called by subclasses that load entities
  1104. /// </summary>
  1105. protected IList LoadEntity(ISessionImplementor session, object id, IType identifierType, object optionalObject,
  1106. string optionalEntityName, object optionalIdentifier, IEntityPersister persister)
  1107. {
  1108. if (Log.IsDebugEnabled)
  1109. {
  1110. Log.Debug("loading entity: " + MessageHelper.InfoString(persister, id, identifierType, Factory));
  1111. }
  1112. IList result;
  1113. try
  1114. {
  1115. QueryParameters qp =
  1116. new QueryParameters(new IType[] { identifierType }, new object[] { id }, optionalObject, optionalEntityName,
  1117. optionalIdentifier);
  1118. result = DoQueryAndInitializeNonLazyCollections(session, qp, false);
  1119. }
  1120. catch (HibernateException)
  1121. {
  1122. throw;
  1123. }
  1124. catch (Exception sqle)
  1125. {
  1126. ILoadable[] persisters = EntityPersisters;
  1127. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle,
  1128. "could not load an entity: "
  1129. +
  1130. MessageHelper.InfoString(persisters[persisters.Length - 1], id, identifierType,
  1131. Factory), SqlString);
  1132. }
  1133. Log.Debug("done entity load");
  1134. return result;
  1135. }
  1136. protected IList LoadEntity(ISessionImplementor session, object key, object index, IType keyType, IType indexType,
  1137. IEntityPersister persister)
  1138. {
  1139. Log.Debug("loading collection element by index");
  1140. IList result;
  1141. try
  1142. {
  1143. result =
  1144. DoQueryAndInitializeNonLazyCollections(session,
  1145. new QueryParameters(new IType[] { keyType, indexType },
  1146. new object[] { key, index }), false);
  1147. }
  1148. catch (Exception sqle)
  1149. {
  1150. throw ADOExceptionHelper.Convert(_factory.SQLExceptionConverter, sqle, "could not collection element by index",
  1151. SqlString);
  1152. }
  1153. Log.Debug("done entity load");
  1154. return result;
  1155. }
  1156. /// <summary>
  1157. /// Called by subclasses that batch load entities
  1158. /// </summary>
  1159. protected internal IList LoadEntityBatch(ISessionImplementor session, object[] ids, IType idType,
  1160. object optionalObject, string optionalEntityName, object optionalId,
  1161. IEntityPersister persister)
  1162. {
  1163. if (Log.IsDebugEnabled)
  1164. {
  1165. Log.Debug("batch loading entity: " + MessageHelper.InfoString(persister, ids, Factory));
  1166. }
  1167. IType[] types = new IType[ids.Length];
  1168. ArrayHelper.Fill(types, idType);
  1169. IList result;
  1170. try
  1171. {
  1172. result =
  1173. DoQueryAndInitializeNonLazyCollections(session,
  1174. new QueryParameters(types, ids, optionalObject, optionalEntityName,
  1175. optionalId), false);
  1176. }
  1177. catch (HibernateException)
  1178. {
  1179. throw;
  1180. }
  1181. catch (Exception sqle)
  1182. {
  1183. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle,
  1184. "could not load an entity batch: "
  1185. + MessageHelper.InfoString(persister, ids, Factory), SqlString);
  1186. // NH: Hibernate3 passes EntityPersisters[0] instead of persister, I think it's wrong.
  1187. }
  1188. Log.Debug("done entity batch load");
  1189. return result;
  1190. }
  1191. /// <summary>
  1192. /// Called by subclasses that load collections
  1193. /// </summary>
  1194. public void LoadCollection(ISessionImplementor session, object id, IType type)
  1195. {
  1196. if (Log.IsDebugEnabled)
  1197. {
  1198. Log.Debug("loading collection: " + MessageHelper.InfoString(CollectionPersisters[0], id));
  1199. }
  1200. object[] ids = new object[] { id };
  1201. try
  1202. {
  1203. DoQueryAndInitializeNonLazyCollections(session, new QueryParameters(new IType[] { type }, ids, ids), true);
  1204. }
  1205. catch (HibernateException)
  1206. {
  1207. // Do not call Convert on HibernateExceptions
  1208. throw;
  1209. }
  1210. catch (Exception sqle)
  1211. {
  1212. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle,
  1213. "could not initialize a collection: "
  1214. + MessageHelper.InfoString(CollectionPersisters[0], id), SqlString);
  1215. }
  1216. Log.Debug("done loading collection");
  1217. }
  1218. /// <summary>
  1219. /// Called by wrappers that batch initialize collections
  1220. /// </summary>
  1221. public void LoadCollectionBatch(ISessionImplementor session, object[] ids, IType type)
  1222. {
  1223. if (Log.IsDebugEnabled)
  1224. {
  1225. Log.Debug("batch loading collection: " + MessageHelper.InfoString(CollectionPersisters[0], ids));
  1226. }
  1227. IType[] idTypes = new IType[ids.Length];
  1228. ArrayHelper.Fill(idTypes, type);
  1229. try
  1230. {
  1231. DoQueryAndInitializeNonLazyCollections(session, new QueryParameters(idTypes, ids, ids), true);
  1232. }
  1233. catch (HibernateException)
  1234. {
  1235. // Do not call Convert on HibernateExceptions
  1236. throw;
  1237. }
  1238. catch (Exception sqle)
  1239. {
  1240. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle,
  1241. "could not initialize a collection batch: "
  1242. + MessageHelper.InfoString(CollectionPersisters[0], ids), SqlString);
  1243. }
  1244. Log.Debug("done batch load");
  1245. }
  1246. /// <summary>
  1247. /// Called by subclasses that batch initialize collections
  1248. /// </summary>
  1249. protected void LoadCollectionSubselect(ISessionImplementor session, object[] ids, object[] parameterValues,
  1250. IType[] parameterTypes, IDictionary<string, TypedValue> namedParameters,
  1251. IType type)
  1252. {
  1253. try
  1254. {
  1255. DoQueryAndInitializeNonLazyCollections(session,
  1256. new QueryParameters(parameterTypes, parameterValues, namedParameters, ids),
  1257. true);
  1258. }
  1259. catch (HibernateException)
  1260. {
  1261. // Do not call Convert on HibernateExceptions
  1262. throw;
  1263. }
  1264. catch (Exception sqle)
  1265. {
  1266. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle,
  1267. "could not load collection by subselect: "
  1268. + MessageHelper.InfoString(CollectionPersisters[0], ids), SqlString,
  1269. parameterValues, namedParameters);
  1270. }
  1271. }
  1272. /// <summary>
  1273. /// Return the query results, using the query cache, called
  1274. /// by subclasses that implement cacheable queries
  1275. /// </summary>
  1276. /// <param name="session"></param>
  1277. /// <param name="queryParameters"></param>
  1278. /// <param name="querySpaces"></param>
  1279. /// <param name="resultTypes"></param>
  1280. /// <returns></returns>
  1281. protected IList List(ISessionImplementor session, QueryParameters queryParameters, ISet<string> querySpaces, IType[] resultTypes)
  1282. {
  1283. bool cacheable = _factory.Settings.IsQueryCacheEnabled && queryParameters.Cacheable;
  1284. if (cacheable)
  1285. {
  1286. return ListUsingQueryCache(session, queryParameters, querySpaces, resultTypes);
  1287. }
  1288. return ListIgnoreQueryCache(session, queryParameters);
  1289. }
  1290. private IList ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters)
  1291. {
  1292. return GetResultList(DoList(session, queryParameters), queryParameters.ResultTransformer);
  1293. }
  1294. private IList ListUsingQueryCache(ISessionImplementor session, QueryParameters queryParameters, ISet<string> querySpaces, IType[] resultTypes)
  1295. {
  1296. IQueryCache queryCache = _factory.GetQueryCache(queryParameters.CacheRegion);
  1297. ISet filterKeys = FilterKey.CreateFilterKeys(session.EnabledFilters, session.EntityMode);
  1298. QueryKey key = new QueryKey(Factory, SqlString, queryParameters, filterKeys);
  1299. IList result = GetResultFromQueryCache(session, queryParameters, querySpaces, resultTypes, queryCache, key);
  1300. if (result == null)
  1301. {
  1302. result = DoList(session, queryParameters);
  1303. PutResultInQueryCache(session, queryParameters, resultTypes, queryCache, key, result);
  1304. }
  1305. return GetResultList(result, queryParameters.ResultTransformer);
  1306. }
  1307. private IList GetResultFromQueryCache(ISessionImplementor session, QueryParameters queryParameters,
  1308. ISet<string> querySpaces, IType[] resultTypes, IQueryCache queryCache,
  1309. QueryKey key)
  1310. {
  1311. IList result = null;
  1312. if ((!queryParameters.ForceCacheRefresh) && (session.CacheMode & CacheMode.Get) == CacheMode.Get)
  1313. {
  1314. IPersistenceContext persistenceContext = session.PersistenceContext;
  1315. bool defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;
  1316. if (queryParameters.IsReadOnlyInitialized)
  1317. persistenceContext.DefaultReadOnly = queryParameters.ReadOnly;
  1318. else
  1319. queryParameters.ReadOnly = persistenceContext.DefaultReadOnly;
  1320. try
  1321. {
  1322. result = queryCache.Get(key, resultTypes, queryParameters.NaturalKeyLookup, querySpaces, session);
  1323. if (_factory.Statistics.IsStatisticsEnabled)
  1324. {
  1325. if (result == null)
  1326. {
  1327. _factory.StatisticsImplementor.QueryCacheMiss(QueryIdentifier, queryCache.RegionName);
  1328. }
  1329. else
  1330. {
  1331. _factory.StatisticsImplementor.QueryCacheHit(QueryIdentifier, queryCache.RegionName);
  1332. }
  1333. }
  1334. }
  1335. finally
  1336. {
  1337. persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
  1338. }
  1339. }
  1340. return result;
  1341. }
  1342. private void PutResultInQueryCache(ISessionImplementor session, QueryParameters queryParameters, IType[] resultTypes,
  1343. IQueryCache queryCache, QueryKey key, IList result)
  1344. {
  1345. if ((session.CacheMode & CacheMode.Put) == CacheMode.Put)
  1346. {
  1347. bool put = queryCache.Put(key, resultTypes, result, queryParameters.NaturalKeyLookup, session);
  1348. if (put && _factory.Statistics.IsStatisticsEnabled)
  1349. {
  1350. _factory.StatisticsImplementor.QueryCachePut(QueryIdentifier, queryCache.RegionName);
  1351. }
  1352. }
  1353. }
  1354. /// <summary>
  1355. /// Actually execute a query, ignoring the query cache
  1356. /// </summary>
  1357. /// <param name="session"></param>
  1358. /// <param name="queryParameters"></param>
  1359. /// <returns></returns>
  1360. protected IList DoList(ISessionImplementor session, QueryParameters queryParameters)
  1361. {
  1362. bool statsEnabled = Factory.Statistics.IsStatisticsEnabled;
  1363. var stopWatch = new Stopwatch();
  1364. if (statsEnabled)
  1365. {
  1366. stopWatch.Start();
  1367. }
  1368. IList result;
  1369. try
  1370. {
  1371. result = DoQueryAndInitializeNonLazyCollections(session, queryParameters, true);
  1372. }
  1373. catch (HibernateException)
  1374. {
  1375. // Do not call Convert on HibernateExceptions
  1376. throw;
  1377. }
  1378. catch (Exception sqle)
  1379. {
  1380. throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, sqle, "could not execute query", SqlString,
  1381. queryParameters.PositionalParameterValues, queryParameters.NamedParameters);
  1382. }
  1383. if (statsEnabled)
  1384. {
  1385. stopWatch.Stop();
  1386. Factory.StatisticsImplementor.QueryExecuted(QueryIdentifier, result.Count, stopWatch.Elapsed);
  1387. }
  1388. return result;
  1389. }
  1390. /// <summary>
  1391. /// Calculate and cache select-clause suffixes. Must be
  1392. /// called by subclasses after instantiation.
  1393. /// </summary>
  1394. protected virtual void PostInstantiate() { }
  1395. /// <summary>
  1396. /// Identifies the query for statistics reporting, if null,
  1397. /// no statistics will be reported
  1398. /// </summary>
  1399. public virtual string QueryIdentifier
  1400. {
  1401. get { return null; }
  1402. }
  1403. public override string ToString()
  1404. {
  1405. return GetType().FullName + '(' + SqlString + ')';
  1406. }
  1407. #region NHibernate specific
  1408. public virtual ISqlCommand CreateSqlCommand(QueryParameters queryParameters, ISessionImplementor session)
  1409. {
  1410. // A distinct-copy of parameter specifications collected during query construction
  1411. var parameterSpecs = new HashSet<IParameterSpecification>(GetParameterSpecifications());
  1412. SqlString sqlString = SqlString.Copy();
  1413. // dynamic-filter parameters: during the createion of the SqlString of allLoader implementation, filters can be added as SQL_TOKEN/string for this reason we have to re-parse the SQL.
  1414. sqlString = ExpandDynamicFilterParameters(sqlString, parameterSpecs, session);
  1415. AdjustQueryParametersForSubSelectFetching(sqlString, parameterSpecs, queryParameters);
  1416. // Add limits
  1417. sqlString = AddLimitsParametersIfNeeded(sqlString, parameterSpecs, queryParameters, session);
  1418. // The PreprocessSQL method can modify the SqlString but should never add parameters (or we have to override it)
  1419. sqlString = PreprocessSQL(sqlString, queryParameters, session.Factory.Dialect);
  1420. // After the last modification to the SqlString we can collect all parameters types (there are cases where we can't infer the type during the creation of the query)
  1421. ResetEffectiveExpectedType(parameterSpecs, queryParameters);
  1422. return new SqlCommandImpl(sqlString, parameterSpecs, queryParameters, session.Factory);
  1423. }
  1424. protected virtual void ResetEffectiveExpectedType(IEnumerable<IParameterSpecification> parameterSpecs, QueryParameters queryParameters)
  1425. {
  1426. // Have to be overridden just by those loaders that can't infer the type during the parse process
  1427. }
  1428. protected abstract IEnumerable<IParameterSpecification> GetParameterSpecifications();
  1429. protected void AdjustQueryParametersForSubSelectFetching(SqlString filteredSqlString, IEnumerable<IParameterSpecification> parameterSpecsWithFilters, QueryParameters queryParameters)
  1430. {
  1431. queryParameters.ProcessedSql = filteredSqlString;
  1432. queryParameters.ProcessedSqlParameters = parameterSpecsWithFilters.ToList();
  1433. if (queryParameters.RowSelection != null)
  1434. {
  1435. queryParameters.ProcessedRowSelection = new RowSelection { FirstRow = queryParameters.RowSelection.FirstRow, MaxRows = queryParameters.RowSelection.MaxRows };
  1436. }
  1437. }
  1438. protected SqlString ExpandDynamicFilterParameters(SqlString sqlString, ICollection<IParameterSpecification> parameterSpecs, ISessionImplementor session)
  1439. {
  1440. var enabledFilters = session.EnabledFilters;
  1441. if (enabledFilters.Count == 0 || sqlString.ToString().IndexOf(ParserHelper.HqlVariablePrefix) < 0)
  1442. {
  1443. return sqlString;
  1444. }
  1445. Dialect.Dialect dialect = session.Factory.Dialect;
  1446. string symbols = ParserHelper.HqlSeparators + dialect.OpenQuote + dialect.CloseQuote;
  1447. var originSql = sqlString.Compact();
  1448. var result = new SqlStringBuilder();
  1449. foreach (var sqlPart in originSql.Parts)
  1450. {
  1451. var parameter = sqlPart as Parameter;
  1452. if (parameter != null)
  1453. {
  1454. result.Add(parameter);
  1455. continue;
  1456. }
  1457. var sqlFragment = sqlPart.ToString();
  1458. var tokens = new StringTokenizer(sqlFragment, symbols, true);
  1459. foreach (string token in tokens)
  1460. {
  1461. if (token.StartsWith(ParserHelper.HqlVariablePrefix))
  1462. {
  1463. string filterParameterName = token.Substring(1);
  1464. string[] parts = StringHelper.ParseFilterParameterName(filterParameterName);
  1465. string filterName = parts[0];
  1466. string parameterName = parts[1];
  1467. var filter = (FilterImpl)enabledFilters[filterName];
  1468. object value = filter.GetParameter(parameterName);
  1469. IType type = filter.FilterDefinition.GetParameterType(parameterName);
  1470. int parameterColumnSpan = type.GetColumnSpan(session.Factory);
  1471. var collectionValue = value as ICollection;
  1472. int? collectionSpan = null;
  1473. // Add query chunk
  1474. string typeBindFragment = string.Join(", ", Enumerable.Repeat("?", parameterColumnSpan).ToArray());
  1475. string bindFragment;
  1476. if (collectionValue != null && !type.ReturnedClass.IsArray)
  1477. {
  1478. collectionSpan = collectionValue.Count;
  1479. bindFragment = string.Join(", ", Enumerable.Repeat(typeBindFragment, collectionValue.Count).ToArray());
  1480. }
  1481. else
  1482. {
  1483. bindFragment = typeBindFragment;
  1484. }
  1485. // dynamic-filter parameter tracking
  1486. var filterParameterFragment = SqlString.Parse(bindFragment);
  1487. var dynamicFilterParameterSpecification = new DynamicFilterParameterSpecification(filterName, parameterName, type, collectionSpan);
  1488. var parameters = filterParameterFragment.GetParameters().ToArray();
  1489. var sqlParameterPos = 0;
  1490. var paramTrackers = dynamicFilterParameterSpecification.GetIdsForBackTrack(session.Factory);
  1491. foreach (var paramTracker in paramTrackers)
  1492. {
  1493. parameters[sqlParameterPos++].BackTrack = paramTracker;
  1494. }
  1495. parameterSpecs.Add(dynamicFilterParameterSpecification);
  1496. result.Add(filterParameterFragment);
  1497. }
  1498. else
  1499. {
  1500. result.Add(token);
  1501. }
  1502. }
  1503. }
  1504. return result.ToSqlString().Compact();
  1505. }
  1506. protected SqlString AddLimitsParametersIfNeeded(SqlString sqlString, ICollection<IParameterSpecification> parameterSpecs, QueryParameters queryParameters, ISessionImplementor session)
  1507. {
  1508. var sessionFactory = session.Factory;
  1509. Dialect.Dialect dialect = sessionFactory.Dialect;
  1510. RowSelection selection = queryParameters.RowSelection;
  1511. bool useLimit = UseLimit(selection, dialect);
  1512. if (useLimit)
  1513. {
  1514. bool hasFirstRow = GetFirstRow(selection) > 0;
  1515. bool useOffset = hasFirstRow && dialect.SupportsLimitOffset;
  1516. int max = GetMaxOrLimit(dialect, selection);
  1517. int? skip = useOffset ? (int?)dialect.GetOffsetValue(GetFirstRow(selection)) : null;
  1518. int? take = max != int.MaxValue ? (int?)max : null;
  1519. Parameter skipSqlParameter = null;
  1520. Parameter takeSqlParameter = null;
  1521. if (skip.HasValue)
  1522. {
  1523. var skipParameter = new QuerySkipParameterSpecification();
  1524. skipSqlParameter = Parameter.Placeholder;
  1525. skipSqlParameter.BackTrack = EnumerableExtensions.First(skipParameter.GetIdsForBackTrack(sessionFactory));
  1526. parameterSpecs.Add(skipParameter);
  1527. }
  1528. if (take.HasValue)
  1529. {
  1530. var takeParameter = new QueryTakeParameterSpecification();
  1531. takeSqlParameter = Parameter.Placeholder;
  1532. takeSqlParameter.BackTrack = EnumerableExtensions.First(takeParameter.GetIdsForBackTrack(sessionFactory));
  1533. parameterSpecs.Add(takeParameter);
  1534. }
  1535. // The dialect can move the given parameters where he need, what it can't do is generates new parameters loosing the BackTrack.
  1536. return dialect.GetLimitString(sqlString, skip, take, skipSqlParameter, takeSqlParameter);
  1537. }
  1538. return sqlString;
  1539. }
  1540. #endregion
  1541. }
  1542. }