PageRenderTime 24ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NHibernate/Multi/QueryBatchItemBase.cs

https://github.com/gliljas/nhibernate-core
C# | 391 lines | 273 code | 67 blank | 51 comment | 35 complexity | 0bf323d6871d0533091512990aadf676 MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Data.Common;
  5. using System.Linq;
  6. using NHibernate.Cache;
  7. using NHibernate.Engine;
  8. using NHibernate.SqlCommand;
  9. using NHibernate.Type;
  10. using NHibernate.Util;
  11. namespace NHibernate.Multi
  12. {
  13. /// <summary>
  14. /// Base class for both ICriteria and IQuery queries
  15. /// </summary>
  16. public abstract partial class QueryBatchItemBase<TResult> : IQueryBatchItem<TResult>, IQueryBatchItemWithAsyncProcessResults
  17. {
  18. private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(QueryBatch));
  19. protected ISessionImplementor Session;
  20. private List<EntityKey[]>[] _subselectResultKeys;
  21. private List<QueryInfo> _queryInfos;
  22. private CacheMode? _cacheMode;
  23. private IList<TResult> _finalResults;
  24. private DbDataReader _reader;
  25. private List<object>[] _hydratedObjects;
  26. protected class QueryInfo : ICachingInformation, ICachingInformationWithFetches
  27. {
  28. /// <summary>
  29. /// The query loader.
  30. /// </summary>
  31. public Loader.Loader Loader { get; set; }
  32. /// <summary>
  33. /// The query result.
  34. /// </summary>
  35. public IList Result { get; set; }
  36. /// <inheritdoc />
  37. public QueryParameters Parameters { get; }
  38. /// <inheritdoc />
  39. public ISet<string> QuerySpaces { get; }
  40. //Cache related properties:
  41. /// <inheritdoc />
  42. public bool IsCacheable { get; }
  43. /// <inheritdoc />
  44. public QueryKey CacheKey { get; }
  45. /// <inheritdoc />
  46. public bool CanGetFromCache { get; }
  47. // Do not store but forward instead: Loader.ResultTypes can be null initially (if AutoDiscoverTypes
  48. // is enabled).
  49. /// <inheritdoc />
  50. public IType[] ResultTypes => Loader.ResultTypes;
  51. /// <inheritdoc />
  52. public IType[] CacheTypes => Loader.CacheTypes;
  53. /// <inheritdoc />
  54. public string QueryIdentifier => Loader.QueryIdentifier;
  55. /// <inheritdoc />
  56. public IList ResultToCache { get; set; }
  57. /// <summary>
  58. /// Indicates if the query result was obtained from the cache.
  59. /// </summary>
  60. public bool IsResultFromCache { get; private set; }
  61. /// <summary>
  62. /// Should a result retrieved from database be cached?
  63. /// </summary>
  64. public bool CanPutToCache { get; }
  65. /// <summary>
  66. /// The cache batcher to use for entities and collections puts.
  67. /// </summary>
  68. public CacheBatcher CacheBatcher { get; private set; }
  69. /// <summary>
  70. /// Create a new <c>QueryInfo</c>.
  71. /// </summary>
  72. /// <param name="parameters">The query parameters.</param>
  73. /// <param name="loader">The loader.</param>
  74. /// <param name="querySpaces">The query spaces.</param>
  75. /// <param name="session">The session of the query.</param>
  76. public QueryInfo(
  77. QueryParameters parameters, Loader.Loader loader, ISet<string> querySpaces,
  78. ISessionImplementor session)
  79. {
  80. Parameters = parameters;
  81. Loader = loader;
  82. QuerySpaces = querySpaces;
  83. IsCacheable = loader.IsCacheable(parameters);
  84. if (!IsCacheable)
  85. return;
  86. CacheKey = Loader.GenerateQueryKey(session, Parameters);
  87. CanGetFromCache = Parameters.CanGetFromCache(session);
  88. CanPutToCache = Parameters.CanPutToCache(session);
  89. }
  90. /// <inheritdoc />
  91. public void SetCachedResult(IList result)
  92. {
  93. if (!IsCacheable)
  94. throw new InvalidOperationException("Cannot set cached result on a non cacheable query");
  95. if (Result != null)
  96. throw new InvalidOperationException("Result is already set");
  97. Result = result;
  98. IsResultFromCache = result != null;
  99. }
  100. /// <inheritdoc />
  101. public void SetCacheBatcher(CacheBatcher cacheBatcher)
  102. {
  103. CacheBatcher = cacheBatcher;
  104. }
  105. }
  106. protected abstract List<QueryInfo> GetQueryInformation(ISessionImplementor session);
  107. /// <inheritdoc />
  108. public IEnumerable<ICachingInformation> CachingInformation
  109. {
  110. get
  111. {
  112. ThrowIfNotInitialized();
  113. return _queryInfos;
  114. }
  115. }
  116. /// <inheritdoc />
  117. public virtual void Init(ISessionImplementor session)
  118. {
  119. Session = session;
  120. _queryInfos = GetQueryInformation(session);
  121. // Cache and readonly parameters are the same for all translators
  122. _cacheMode = _queryInfos.First().Parameters.CacheMode;
  123. var count = _queryInfos.Count;
  124. _subselectResultKeys = new List<EntityKey[]>[count];
  125. _finalResults = null;
  126. }
  127. /// <inheritdoc />
  128. public IEnumerable<string> GetQuerySpaces()
  129. {
  130. return _queryInfos.SelectMany(q => q.QuerySpaces);
  131. }
  132. /// <inheritdoc />
  133. public IEnumerable<ISqlCommand> GetCommands()
  134. {
  135. ThrowIfNotInitialized();
  136. foreach (var qi in _queryInfos)
  137. {
  138. if (qi.IsResultFromCache)
  139. continue;
  140. yield return qi.Loader.CreateSqlCommand(qi.Parameters, Session);
  141. }
  142. }
  143. /// <inheritdoc />
  144. public int ProcessResultsSet(DbDataReader reader)
  145. {
  146. ThrowIfNotInitialized();
  147. var dialect = Session.Factory.Dialect;
  148. var hydratedObjects = new List<object>[_queryInfos.Count];
  149. var isDebugLog = Log.IsDebugEnabled();
  150. using (Session.SwitchCacheMode(_cacheMode))
  151. {
  152. var rowCount = 0;
  153. for (var i = 0; i < _queryInfos.Count; i++)
  154. {
  155. var queryInfo = _queryInfos[i];
  156. var loader = queryInfo.Loader;
  157. var queryParameters = queryInfo.Parameters;
  158. //Skip processing for items already loaded from cache
  159. if (queryInfo.IsResultFromCache)
  160. {
  161. continue;
  162. }
  163. var entitySpan = loader.EntityPersisters.Length;
  164. hydratedObjects[i] = entitySpan == 0 ? null : new List<object>(entitySpan);
  165. var keys = new EntityKey[entitySpan];
  166. var selection = queryParameters.RowSelection;
  167. var createSubselects = loader.IsSubselectLoadingEnabled;
  168. _subselectResultKeys[i] = createSubselects ? new List<EntityKey[]>() : null;
  169. var maxRows = Loader.Loader.HasMaxRows(selection) ? selection.MaxRows : int.MaxValue;
  170. var advanceSelection = !dialect.SupportsLimitOffset || !loader.UseLimit(selection, dialect);
  171. if (advanceSelection)
  172. {
  173. Loader.Loader.Advance(reader, selection);
  174. }
  175. var forcedResultTransformer = queryInfo.CacheKey?.ResultTransformer;
  176. if (queryParameters.HasAutoDiscoverScalarTypes)
  177. {
  178. loader.AutoDiscoverTypes(reader, queryParameters, forcedResultTransformer);
  179. }
  180. var lockModeArray = loader.GetLockModes(queryParameters.LockModes);
  181. var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session);
  182. var tmpResults = new List<object>();
  183. var queryCacheBuilder = queryInfo.IsCacheable ? new QueryCacheResultBuilder(loader) : null;
  184. var cacheBatcher = queryInfo.CacheBatcher;
  185. var ownCacheBatcher = cacheBatcher == null;
  186. if (ownCacheBatcher)
  187. cacheBatcher = new CacheBatcher(Session);
  188. if (isDebugLog)
  189. Log.Debug("processing result set");
  190. int count;
  191. for (count = 0; count < maxRows && reader.Read(); count++)
  192. {
  193. if (isDebugLog)
  194. Log.Debug("result set row: {0}", count);
  195. rowCount++;
  196. var o =
  197. loader.GetRowFromResultSet(
  198. reader,
  199. Session,
  200. queryParameters,
  201. lockModeArray,
  202. optionalObjectKey,
  203. hydratedObjects[i],
  204. keys,
  205. true,
  206. forcedResultTransformer,
  207. queryCacheBuilder,
  208. (persister, data) => cacheBatcher.AddToBatch(persister, data)
  209. );
  210. if (loader.IsSubselectLoadingEnabled)
  211. {
  212. _subselectResultKeys[i].Add(keys);
  213. keys = new EntityKey[entitySpan]; //can't reuse in this case
  214. }
  215. tmpResults.Add(o);
  216. }
  217. if (isDebugLog)
  218. Log.Debug("done processing result set ({0} rows)", count);
  219. queryInfo.Result = tmpResults;
  220. if (queryInfo.CanPutToCache)
  221. queryInfo.ResultToCache = queryCacheBuilder.Result;
  222. if (ownCacheBatcher)
  223. cacheBatcher.ExecuteBatch();
  224. reader.NextResult();
  225. }
  226. StopLoadingCollections(reader);
  227. _reader = reader;
  228. _hydratedObjects = hydratedObjects;
  229. return rowCount;
  230. }
  231. }
  232. /// <inheritdoc cref="IQueryBatchItem.ProcessResults" />
  233. public void ProcessResults()
  234. {
  235. ThrowIfNotInitialized();
  236. using (Session.SwitchCacheMode(_cacheMode))
  237. InitializeEntitiesAndCollections(_reader, _hydratedObjects);
  238. for (var i = 0; i < _queryInfos.Count; i++)
  239. {
  240. var queryInfo = _queryInfos[i];
  241. if (_subselectResultKeys[i] != null)
  242. {
  243. queryInfo.Loader.CreateSubselects(_subselectResultKeys[i], queryInfo.Parameters, Session);
  244. }
  245. if (queryInfo.IsCacheable)
  246. {
  247. if (queryInfo.IsResultFromCache)
  248. {
  249. var queryCacheBuilder = new QueryCacheResultBuilder(queryInfo.Loader);
  250. queryInfo.Result = queryCacheBuilder.GetResultList(queryInfo.Result);
  251. }
  252. // This transformation must not be applied to ResultToCache.
  253. queryInfo.Result =
  254. queryInfo.Loader.TransformCacheableResults(
  255. queryInfo.Parameters, queryInfo.CacheKey.ResultTransformer, queryInfo.Result);
  256. }
  257. }
  258. AfterLoadCallback?.Invoke(GetResults());
  259. }
  260. /// <inheritdoc />
  261. public void ExecuteNonBatched()
  262. {
  263. _finalResults = GetResultsNonBatched();
  264. AfterLoadCallback?.Invoke(_finalResults);
  265. }
  266. protected abstract IList<TResult> GetResultsNonBatched();
  267. protected List<T> GetTypedResults<T>()
  268. {
  269. ThrowIfNotInitialized();
  270. if (_queryInfos.Any(qi => qi.Result == null))
  271. {
  272. throw new InvalidOperationException("Some query results are missing, batch is likely not fully executed yet.");
  273. }
  274. var results = new List<T>(_queryInfos.Sum(qi => qi.Result.Count));
  275. foreach (var queryInfo in _queryInfos)
  276. {
  277. var list = queryInfo.Loader.GetResultList(
  278. queryInfo.Result,
  279. queryInfo.Parameters.ResultTransformer);
  280. ArrayHelper.AddAll(results, list);
  281. }
  282. return results;
  283. }
  284. /// <inheritdoc />
  285. public IList<TResult> GetResults()
  286. {
  287. return _finalResults ?? (_finalResults = DoGetResults());
  288. }
  289. /// <inheritdoc />
  290. public Action<IList<TResult>> AfterLoadCallback { get; set; }
  291. protected abstract List<TResult> DoGetResults();
  292. private void InitializeEntitiesAndCollections(DbDataReader reader, List<object>[] hydratedObjects)
  293. {
  294. for (var i = 0; i < _queryInfos.Count; i++)
  295. {
  296. var queryInfo = _queryInfos[i];
  297. if (queryInfo.IsResultFromCache)
  298. continue;
  299. queryInfo.Loader.InitializeEntitiesAndCollections(
  300. hydratedObjects[i], reader, Session, queryInfo.Parameters.IsReadOnly(Session),
  301. queryInfo.CacheBatcher);
  302. }
  303. }
  304. private void StopLoadingCollections(DbDataReader reader)
  305. {
  306. for (var i = 0; i < _queryInfos.Count; i++)
  307. {
  308. var queryInfo = _queryInfos[i];
  309. if (queryInfo.IsResultFromCache)
  310. continue;
  311. queryInfo.Loader.StopLoadingCollections(Session, reader);
  312. }
  313. }
  314. private void ThrowIfNotInitialized()
  315. {
  316. if (_queryInfos == null)
  317. throw new InvalidOperationException(
  318. "The query item has not been initialized. A query item must belong to a batch " +
  319. $"({nameof(IQueryBatch)}) and the batch must be executed ({nameof(IQueryBatch)}." +
  320. $"{nameof(IQueryBatch.Execute)} or {nameof(IQueryBatch)}.{nameof(IQueryBatch.GetResult)}) " +
  321. "before retrieving the item result.");
  322. }
  323. }
  324. }