diff --git a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs index 87261158243..2fe7614ce42 100644 --- a/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Async/Futures/QueryBatchFixture.cs @@ -388,6 +388,116 @@ public async Task UsingHqlToFutureWithCacheAndTransformerDoesntThrowAsync() } } + [Test] + public async Task ReadOnlyWorksWithFutureAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var futureSimples = + s + .CreateQuery("from EntitySimpleChild") + .SetReadOnly(true) + .Future(); + var futureSubselect = + s + .CreateQuery("from EntitySubselectChild") + .Future(); + + var simples = (await (futureSimples.GetEnumerableAsync())).ToList(); + Assert.That(simples, Has.Count.GreaterThan(0)); + foreach (var entity in simples) + { + Assert.That(s.IsReadOnly(entity), Is.True, entity.Name); + } + + var subselect = (await (futureSubselect.GetEnumerableAsync())).ToList(); + Assert.That(subselect, Has.Count.GreaterThan(0)); + foreach (var entity in subselect) + { + Assert.That(s.IsReadOnly(entity), Is.False, entity.Name); + } + + await (t.CommitAsync()); + } + } + + [Test] + public async Task CacheModeWorksWithFutureAsync() + { + Sfi.Statistics.IsStatisticsEnabled = true; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s + .CreateQuery("from EntitySimpleChild") + .SetCacheable(true) + .SetCacheMode(CacheMode.Get) + .Future(); + s + .CreateQuery("from EntityComplex") + .SetCacheable(true) + .SetCacheMode(CacheMode.Put) + .Future(); + await (s + .CreateQuery("from EntitySubselectChild") + .SetCacheable(true) + .Future() + .GetEnumerableAsync()); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Future put"); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Sfi.Statistics.Clear(); + await (s + .CreateQuery("from EntitySimpleChild") + .SetCacheable(true) + .ListAsync()); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "EntitySimpleChild query hit"); + + Sfi.Statistics.Clear(); + await (s + .CreateQuery("from EntityComplex") + .SetCacheable(true) + .ListAsync()); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "EntityComplex query hit"); + + Sfi.Statistics.Clear(); + await (s + .CreateQuery("from EntitySubselectChild") + .SetCacheable(true) + .ListAsync()); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "EntitySubselectChild query hit"); + + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Sfi.Statistics.Clear(); + s + .CreateQuery("from EntitySimpleChild") + .SetCacheable(true) + .SetCacheMode(CacheMode.Get) + .Future(); + await (s + .CreateQuery("from EntitySubselectChild") + .SetCacheable(true) + .Future() + .GetEnumerableAsync()); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Second future put"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Second future hit"); + + await (t.CommitAsync()); + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -471,6 +581,7 @@ protected override void OnTearDown() session.Flush(); transaction.Commit(); } + Sfi.Statistics.IsStatisticsEnabled = false; } protected override void OnSetUp() diff --git a/src/NHibernate.Test/Futures/QueryBatchFixture.cs b/src/NHibernate.Test/Futures/QueryBatchFixture.cs index c872d8bc2fc..cb79faedae1 100644 --- a/src/NHibernate.Test/Futures/QueryBatchFixture.cs +++ b/src/NHibernate.Test/Futures/QueryBatchFixture.cs @@ -376,6 +376,116 @@ public void UsingHqlToFutureWithCacheAndTransformerDoesntThrow() } } + [Test] + public void ReadOnlyWorksWithFuture() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var futureSimples = + s + .CreateQuery("from EntitySimpleChild") + .SetReadOnly(true) + .Future(); + var futureSubselect = + s + .CreateQuery("from EntitySubselectChild") + .Future(); + + var simples = futureSimples.GetEnumerable().ToList(); + Assert.That(simples, Has.Count.GreaterThan(0)); + foreach (var entity in simples) + { + Assert.That(s.IsReadOnly(entity), Is.True, entity.Name); + } + + var subselect = futureSubselect.GetEnumerable().ToList(); + Assert.That(subselect, Has.Count.GreaterThan(0)); + foreach (var entity in subselect) + { + Assert.That(s.IsReadOnly(entity), Is.False, entity.Name); + } + + t.Commit(); + } + } + + [Test] + public void CacheModeWorksWithFuture() + { + Sfi.Statistics.IsStatisticsEnabled = true; + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s + .CreateQuery("from EntitySimpleChild") + .SetCacheable(true) + .SetCacheMode(CacheMode.Get) + .Future(); + s + .CreateQuery("from EntityComplex") + .SetCacheable(true) + .SetCacheMode(CacheMode.Put) + .Future(); + s + .CreateQuery("from EntitySubselectChild") + .SetCacheable(true) + .Future() + .GetEnumerable(); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(2), "Future put"); + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Sfi.Statistics.Clear(); + s + .CreateQuery("from EntitySimpleChild") + .SetCacheable(true) + .List(); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(0), "EntitySimpleChild query hit"); + + Sfi.Statistics.Clear(); + s + .CreateQuery("from EntityComplex") + .SetCacheable(true) + .List(); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "EntityComplex query hit"); + + Sfi.Statistics.Clear(); + s + .CreateQuery("from EntitySubselectChild") + .SetCacheable(true) + .List(); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "EntitySubselectChild query hit"); + + t.Commit(); + } + + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Sfi.Statistics.Clear(); + s + .CreateQuery("from EntitySimpleChild") + .SetCacheable(true) + .SetCacheMode(CacheMode.Get) + .Future(); + s + .CreateQuery("from EntitySubselectChild") + .SetCacheable(true) + .Future() + .GetEnumerable(); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Second future put"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(2), "Second future hit"); + + t.Commit(); + } + } + #region Test Setup protected override HbmMapping GetMappings() @@ -459,6 +569,7 @@ protected override void OnTearDown() session.Flush(); transaction.Commit(); } + Sfi.Statistics.IsStatisticsEnabled = false; } protected override void OnSetUp() diff --git a/src/NHibernate/Async/Cache/StandardQueryCache.cs b/src/NHibernate/Async/Cache/StandardQueryCache.cs index 81a3dfabe92..5cc5213a8a2 100644 --- a/src/NHibernate/Async/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Async/Cache/StandardQueryCache.cs @@ -245,13 +245,23 @@ public async Task GetManyAsync( else queryParams.ReadOnly = persistenceContext.DefaultReadOnly; - try + // Adjust the session cache mode, as GetResultFromCacheable assemble types which may cause + // entity loads, which may interact with the cache. + using (session.SwitchCacheMode(queryParams.CacheMode)) { - results[i] = await (GetResultFromCacheableAsync(key, returnTypes[i], queryParams.NaturalKeyLookup, session, cacheable, cancellationToken)).ConfigureAwait(false); - } - finally - { - persistenceContext.DefaultReadOnly = defaultReadOnlyOrig; + try + { + results[i] = await (GetResultFromCacheableAsync( + key, + returnTypes[i], + queryParams.NaturalKeyLookup, + session, + cacheable, cancellationToken)).ConfigureAwait(false); + } + finally + { + persistenceContext.DefaultReadOnly = defaultReadOnlyOrig; + } } } diff --git a/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs b/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs index afd64e3a6b2..fec2ff09a11 100644 --- a/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs +++ b/src/NHibernate/Async/Event/Default/DefaultDeleteEventListener.cs @@ -222,40 +222,42 @@ protected virtual async Task DeleteEntityAsync(IEventSource session, object enti protected virtual async Task CascadeBeforeDeleteAsync(IEventSource session, IEntityPersister persister, object entity, EntityEntry entityEntry, ISet transientEntities, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - ISessionImplementor si = session; - CacheMode cacheMode = si.CacheMode; - si.CacheMode = CacheMode.Get; - session.PersistenceContext.IncrementCascadeLevel(); - try + using (session.SwitchCacheMode(CacheMode.Get)) { - // cascade-delete to collections BEFORE the collection owner is deleted - await (new Cascade(CascadingAction.Delete, CascadePoint.AfterInsertBeforeDelete, session).CascadeOnAsync(persister, entity, - transientEntities, cancellationToken)).ConfigureAwait(false); - } - finally - { - session.PersistenceContext.DecrementCascadeLevel(); - si.CacheMode = cacheMode; + session.PersistenceContext.IncrementCascadeLevel(); + try + { + // cascade-delete to collections BEFORE the collection owner is deleted + await (new Cascade(CascadingAction.Delete, CascadePoint.AfterInsertBeforeDelete, session).CascadeOnAsync( + persister, + entity, + transientEntities, cancellationToken)).ConfigureAwait(false); + } + finally + { + session.PersistenceContext.DecrementCascadeLevel(); + } } } protected virtual async Task CascadeAfterDeleteAsync(IEventSource session, IEntityPersister persister, object entity, ISet transientEntities, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - ISessionImplementor si = session; - CacheMode cacheMode = si.CacheMode; - si.CacheMode = CacheMode.Get; - session.PersistenceContext.IncrementCascadeLevel(); - try + using (session.SwitchCacheMode(CacheMode.Get)) { - // cascade-delete to many-to-one AFTER the parent was deleted - await (new Cascade(CascadingAction.Delete, CascadePoint.BeforeInsertAfterDelete, session).CascadeOnAsync(persister, entity, - transientEntities, cancellationToken)).ConfigureAwait(false); - } - finally - { - session.PersistenceContext.DecrementCascadeLevel(); - si.CacheMode = cacheMode; + session.PersistenceContext.IncrementCascadeLevel(); + try + { + // cascade-delete to many-to-one AFTER the parent was deleted + await (new Cascade(CascadingAction.Delete, CascadePoint.BeforeInsertAfterDelete, session).CascadeOnAsync( + persister, + entity, + transientEntities, cancellationToken)).ConfigureAwait(false); + } + finally + { + session.PersistenceContext.DecrementCascadeLevel(); + } } } } diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index 58af26e2c36..04413af7f30 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -1227,7 +1227,7 @@ private async Task GetResultFromQueryCacheAsync( IQueryCache queryCache, QueryKey key, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (!CanGetFromCache(session, queryParameters)) + if (!queryParameters.CanGetFromCache(session)) return null; var result = await (queryCache.GetAsync( @@ -1256,7 +1256,7 @@ private async Task PutResultInQueryCacheAsync(ISessionImplementor session, Query IQueryCache queryCache, QueryKey key, IList result, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (!session.CacheMode.HasFlag(CacheMode.Put)) + if (!queryParameters.CanPutToCache(session)) return; var put = await (queryCache.PutAsync( diff --git a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs index 9360351debb..47e7bf0a952 100644 --- a/src/NHibernate/Async/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Async/Multi/QueryBatchItemBase.cs @@ -35,80 +35,83 @@ public async Task ProcessResultsSetAsync(DbDataReader reader, CancellationT var dialect = Session.Factory.Dialect; var hydratedObjects = new List[_queryInfos.Count]; - var rowCount = 0; - for (var i = 0; i < _queryInfos.Count; i++) + using (Session.SwitchCacheMode(_cacheMode)) { - var queryInfo = _queryInfos[i]; - var loader = queryInfo.Loader; - var queryParameters = queryInfo.Parameters; - - //Skip processing for items already loaded from cache - if (queryInfo.IsResultFromCache) + var rowCount = 0; + for (var i = 0; i < _queryInfos.Count; i++) { - continue; - } + var queryInfo = _queryInfos[i]; + var loader = queryInfo.Loader; + var queryParameters = queryInfo.Parameters; - var entitySpan = loader.EntityPersisters.Length; - hydratedObjects[i] = entitySpan == 0 ? null : new List(entitySpan); - var keys = new EntityKey[entitySpan]; + //Skip processing for items already loaded from cache + if (queryInfo.IsResultFromCache) + { + continue; + } - var selection = queryParameters.RowSelection; - var createSubselects = loader.IsSubselectLoadingEnabled; + var entitySpan = loader.EntityPersisters.Length; + hydratedObjects[i] = entitySpan == 0 ? null : new List(entitySpan); + var keys = new EntityKey[entitySpan]; - _subselectResultKeys[i] = createSubselects ? new List() : null; - var maxRows = Loader.Loader.HasMaxRows(selection) ? selection.MaxRows : int.MaxValue; - var advanceSelection = !dialect.SupportsLimitOffset || !loader.UseLimit(selection, dialect); + var selection = queryParameters.RowSelection; + var createSubselects = loader.IsSubselectLoadingEnabled; - if (advanceSelection) - { - await (Loader.Loader.AdvanceAsync(reader, selection, cancellationToken)).ConfigureAwait(false); - } + _subselectResultKeys[i] = createSubselects ? new List() : null; + var maxRows = Loader.Loader.HasMaxRows(selection) ? selection.MaxRows : int.MaxValue; + var advanceSelection = !dialect.SupportsLimitOffset || !loader.UseLimit(selection, dialect); - var forcedResultTransformer = queryInfo.CacheKey?.ResultTransformer; - if (queryParameters.HasAutoDiscoverScalarTypes) - { - loader.AutoDiscoverTypes(reader, queryParameters, forcedResultTransformer); - } + if (advanceSelection) + { + await (Loader.Loader.AdvanceAsync(reader, selection, cancellationToken)).ConfigureAwait(false); + } + + var forcedResultTransformer = queryInfo.CacheKey?.ResultTransformer; + if (queryParameters.HasAutoDiscoverScalarTypes) + { + loader.AutoDiscoverTypes(reader, queryParameters, forcedResultTransformer); + } - var lockModeArray = loader.GetLockModes(queryParameters.LockModes); - var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); - var tmpResults = new List(); + var lockModeArray = loader.GetLockModes(queryParameters.LockModes); + var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); + var tmpResults = new List(); - for (var count = 0; count < maxRows && await (reader.ReadAsync(cancellationToken)).ConfigureAwait(false); count++) - { - rowCount++; - - var o = - await (loader.GetRowFromResultSetAsync( - reader, - Session, - queryParameters, - lockModeArray, - optionalObjectKey, - hydratedObjects[i], - keys, - true, - forcedResultTransformer -, cancellationToken )).ConfigureAwait(false); - if (loader.IsSubselectLoadingEnabled) + for (var count = 0; count < maxRows && await (reader.ReadAsync(cancellationToken)).ConfigureAwait(false); count++) { - _subselectResultKeys[i].Add(keys); - keys = new EntityKey[entitySpan]; //can't reuse in this case + rowCount++; + + var o = + await (loader.GetRowFromResultSetAsync( + reader, + Session, + queryParameters, + lockModeArray, + optionalObjectKey, + hydratedObjects[i], + keys, + true, + forcedResultTransformer +, cancellationToken )).ConfigureAwait(false); + if (loader.IsSubselectLoadingEnabled) + { + _subselectResultKeys[i].Add(keys); + keys = new EntityKey[entitySpan]; //can't reuse in this case + } + + tmpResults.Add(o); } - tmpResults.Add(o); + queryInfo.Result = tmpResults; + if (queryInfo.CanPutToCache) + queryInfo.ResultToCache = tmpResults; + + await (reader.NextResultAsync(cancellationToken)).ConfigureAwait(false); } - queryInfo.Result = tmpResults; - if (queryInfo.CanPutToCache) - queryInfo.ResultToCache = tmpResults; + await (InitializeEntitiesAndCollectionsAsync(reader, hydratedObjects, cancellationToken)).ConfigureAwait(false); - await (reader.NextResultAsync(cancellationToken)).ConfigureAwait(false); + return rowCount; } - - await (InitializeEntitiesAndCollectionsAsync(reader, hydratedObjects, cancellationToken)).ConfigureAwait(false); - - return rowCount; } /// diff --git a/src/NHibernate/Cache/StandardQueryCache.cs b/src/NHibernate/Cache/StandardQueryCache.cs index 453101bc13c..47c831772ba 100644 --- a/src/NHibernate/Cache/StandardQueryCache.cs +++ b/src/NHibernate/Cache/StandardQueryCache.cs @@ -260,13 +260,23 @@ public IList[] GetMany( else queryParams.ReadOnly = persistenceContext.DefaultReadOnly; - try + // Adjust the session cache mode, as GetResultFromCacheable assemble types which may cause + // entity loads, which may interact with the cache. + using (session.SwitchCacheMode(queryParams.CacheMode)) { - results[i] = GetResultFromCacheable(key, returnTypes[i], queryParams.NaturalKeyLookup, session, cacheable); - } - finally - { - persistenceContext.DefaultReadOnly = defaultReadOnlyOrig; + try + { + results[i] = GetResultFromCacheable( + key, + returnTypes[i], + queryParams.NaturalKeyLookup, + session, + cacheable); + } + finally + { + persistenceContext.DefaultReadOnly = defaultReadOnlyOrig; + } } } diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 530f146b261..c9b6e36fe40 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -18,7 +18,7 @@ namespace NHibernate.Engine { - // 6.0 TODO: Convert to interface methods + // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode internal static partial class SessionImplementorExtensions { internal static IDisposable BeginContext(this ISessionImplementor session) @@ -34,7 +34,7 @@ internal static IDisposable BeginProcess(this ISessionImplementor session) return null; return (session as AbstractSessionImpl)?.BeginProcess() ?? // This method has only replaced bare call to setting the id, so this fallback is enough for avoiding a - // breaking change in case in custom session implementation is used. + // breaking change in case a custom session implementation is used. new SessionIdLoggingContext(session.SessionId); } @@ -48,6 +48,41 @@ internal static void AutoFlushIfRequired(this ISessionImplementor implementor, I { (implementor as AbstractSessionImpl)?.AutoFlushIfRequired(querySpaces); } + + /// + /// Switch the session current cache mode. + /// + /// The session for which the cache mode has to be switched. + /// The desired cache mode. for not actually switching. + /// if no switch is required, otherwise an which + /// dispose will set the session cache mode back to its original value. + internal static IDisposable SwitchCacheMode(this ISessionImplementor session, CacheMode? cacheMode) + { + if (!cacheMode.HasValue || cacheMode == session.CacheMode) + return null; + return new CacheModeSwitch(session, cacheMode.Value); + } + + private sealed class CacheModeSwitch : IDisposable + { + private ISessionImplementor _session; + private readonly CacheMode _originalCacheMode; + + public CacheModeSwitch(ISessionImplementor session, CacheMode cacheMode) + { + _session = session; + _originalCacheMode = session.CacheMode; + _session.CacheMode = cacheMode; + } + + public void Dispose() + { + if (_session == null) + throw new ObjectDisposedException("The session cache mode switch has been disposed already"); + _session.CacheMode = _originalCacheMode; + _session = null; + } + } } /// diff --git a/src/NHibernate/Engine/QueryParameters.cs b/src/NHibernate/Engine/QueryParameters.cs index 9417b0b2346..517e4770607 100644 --- a/src/NHibernate/Engine/QueryParameters.cs +++ b/src/NHibernate/Engine/QueryParameters.cs @@ -7,7 +7,6 @@ using NHibernate.SqlCommand; using NHibernate.Transform; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Engine { @@ -124,6 +123,8 @@ public bool HasRowSelection public string CacheRegion { get; set; } + public CacheMode? CacheMode { get; set; } + public string Comment { get; set; } public bool ForceCacheRefresh { get; set; } @@ -201,13 +202,15 @@ public void ValidateParameters() public QueryParameters CreateCopyUsing(RowSelection selection) { - var copy = new QueryParameters(PositionalParameterTypes, PositionalParameterValues, NamedParameters, LockModes, - selection, IsReadOnlyInitialized, readOnly, Cacheable, CacheRegion, Comment, CollectionKeys, - OptionalObject, OptionalEntityName, OptionalId, ResultTransformer) - { - ProcessedSql = ProcessedSql, - ProcessedSqlParameters = ProcessedSqlParameters != null ? ProcessedSqlParameters.ToList() : null - }; + var copy = new QueryParameters( + PositionalParameterTypes, PositionalParameterValues, NamedParameters, LockModes, selection, + IsReadOnlyInitialized, readOnly, Cacheable, CacheRegion, Comment, CollectionKeys, OptionalObject, + OptionalEntityName, OptionalId, ResultTransformer) + { + ProcessedSql = ProcessedSql, + ProcessedSqlParameters = ProcessedSqlParameters != null ? ProcessedSqlParameters.ToList() : null, + CacheMode = CacheMode + }; return copy; } @@ -215,5 +218,15 @@ public bool IsReadOnly(ISessionImplementor session) { return IsReadOnlyInitialized ? ReadOnly : session.PersistenceContext.DefaultReadOnly; } + + public bool CanGetFromCache(ISessionImplementor session) + { + return !ForceCacheRefresh && (CacheMode ?? session.CacheMode).HasFlag(NHibernate.CacheMode.Get); + } + + public bool CanPutToCache(ISessionImplementor session) + { + return (CacheMode ?? session.CacheMode).HasFlag(NHibernate.CacheMode.Put); + } } } diff --git a/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs b/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs index 6a0df1b5665..c4f699f863f 100644 --- a/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs +++ b/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs @@ -242,39 +242,41 @@ protected virtual bool InvokeDeleteLifecycle(IEventSource session, object entity protected virtual void CascadeBeforeDelete(IEventSource session, IEntityPersister persister, object entity, EntityEntry entityEntry, ISet transientEntities) { - ISessionImplementor si = session; - CacheMode cacheMode = si.CacheMode; - si.CacheMode = CacheMode.Get; - session.PersistenceContext.IncrementCascadeLevel(); - try + using (session.SwitchCacheMode(CacheMode.Get)) { - // cascade-delete to collections BEFORE the collection owner is deleted - new Cascade(CascadingAction.Delete, CascadePoint.AfterInsertBeforeDelete, session).CascadeOn(persister, entity, - transientEntities); - } - finally - { - session.PersistenceContext.DecrementCascadeLevel(); - si.CacheMode = cacheMode; + session.PersistenceContext.IncrementCascadeLevel(); + try + { + // cascade-delete to collections BEFORE the collection owner is deleted + new Cascade(CascadingAction.Delete, CascadePoint.AfterInsertBeforeDelete, session).CascadeOn( + persister, + entity, + transientEntities); + } + finally + { + session.PersistenceContext.DecrementCascadeLevel(); + } } } protected virtual void CascadeAfterDelete(IEventSource session, IEntityPersister persister, object entity, ISet transientEntities) { - ISessionImplementor si = session; - CacheMode cacheMode = si.CacheMode; - si.CacheMode = CacheMode.Get; - session.PersistenceContext.IncrementCascadeLevel(); - try + using (session.SwitchCacheMode(CacheMode.Get)) { - // cascade-delete to many-to-one AFTER the parent was deleted - new Cascade(CascadingAction.Delete, CascadePoint.BeforeInsertAfterDelete, session).CascadeOn(persister, entity, - transientEntities); - } - finally - { - session.PersistenceContext.DecrementCascadeLevel(); - si.CacheMode = cacheMode; + session.PersistenceContext.IncrementCascadeLevel(); + try + { + // cascade-delete to many-to-one AFTER the parent was deleted + new Cascade(CascadingAction.Delete, CascadePoint.BeforeInsertAfterDelete, session).CascadeOn( + persister, + entity, + transientEntities); + } + finally + { + session.PersistenceContext.DecrementCascadeLevel(); + } } } } diff --git a/src/NHibernate/Impl/AbstractQueryImpl.cs b/src/NHibernate/Impl/AbstractQueryImpl.cs index 3fa74af00f2..788db5cbda6 100644 --- a/src/NHibernate/Impl/AbstractQueryImpl.cs +++ b/src/NHibernate/Impl/AbstractQueryImpl.cs @@ -997,21 +997,24 @@ public virtual QueryParameters GetQueryParameters() public virtual QueryParameters GetQueryParameters(IDictionary namedParams) { return new QueryParameters( - TypeArray(), - ValueArray(), - namedParams, - LockModes, - Selection, - true, - IsReadOnly, - cacheable, - cacheRegion, - comment, - collectionKey == null ? null : new[] { collectionKey }, - optionalObject, - optionalEntityName, - optionalId, - resultTransformer); + TypeArray(), + ValueArray(), + namedParams, + LockModes, + Selection, + true, + IsReadOnly, + cacheable, + cacheRegion, + comment, + collectionKey == null ? null : new[] { collectionKey }, + optionalObject, + optionalEntityName, + optionalId, + resultTransformer) + { + CacheMode = cacheMode + }; } protected void Before() diff --git a/src/NHibernate/Impl/CriteriaImpl.cs b/src/NHibernate/Impl/CriteriaImpl.cs index 5a6d4547aa7..511ca3e651f 100644 --- a/src/NHibernate/Impl/CriteriaImpl.cs +++ b/src/NHibernate/Impl/CriteriaImpl.cs @@ -202,6 +202,8 @@ public string CacheRegion get { return cacheRegion; } } + public CacheMode? CacheMode => cacheMode; + public string Comment { get { return comment; } diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index 5b4199b0e30..c46efecfc8f 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -167,7 +167,10 @@ public QueryParameters GetQueryParameters() rootCriteria.CacheRegion, rootCriteria.Comment, rootCriteria.LookupByNaturalKey, - rootCriteria.ResultTransformer); + rootCriteria.ResultTransformer) + { + CacheMode = rootCriteria.CacheMode + }; } public SqlString GetGroupBy() diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index c63bd9fbcbf..a05ffc3eda1 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1714,16 +1714,11 @@ private CacheableResultTransformer CreateCacheableResultTransformer(QueryParamet queryParameters.HasAutoDiscoverScalarTypes, SqlString); } - internal bool CanGetFromCache(ISessionImplementor session, QueryParameters queryParameters) - { - return !queryParameters.ForceCacheRefresh && session.CacheMode.HasFlag(CacheMode.Get); - } - private IList GetResultFromQueryCache( ISessionImplementor session, QueryParameters queryParameters, ISet querySpaces, IQueryCache queryCache, QueryKey key) { - if (!CanGetFromCache(session, queryParameters)) + if (!queryParameters.CanGetFromCache(session)) return null; var result = queryCache.Get( @@ -1751,7 +1746,7 @@ private IList GetResultFromQueryCache( private void PutResultInQueryCache(ISessionImplementor session, QueryParameters queryParameters, IQueryCache queryCache, QueryKey key, IList result) { - if (!session.CacheMode.HasFlag(CacheMode.Put)) + if (!queryParameters.CanPutToCache(session)) return; var put = queryCache.Put( diff --git a/src/NHibernate/Multi/QueryBatchItemBase.cs b/src/NHibernate/Multi/QueryBatchItemBase.cs index 89407c582a2..273070d3f15 100644 --- a/src/NHibernate/Multi/QueryBatchItemBase.cs +++ b/src/NHibernate/Multi/QueryBatchItemBase.cs @@ -19,6 +19,7 @@ public abstract partial class QueryBatchItemBase : IQueryBatchItem[] _subselectResultKeys; private List _queryInfos; + private CacheMode? _cacheMode; private IList _finalResults; protected class QueryInfo : ICachingInformation @@ -96,8 +97,8 @@ public QueryInfo( return; CacheKey = Loader.GenerateQueryKey(session, Parameters); - CanGetFromCache = Loader.CanGetFromCache(session, Parameters); - CanPutToCache = session.CacheMode.HasFlag(CacheMode.Put); + CanGetFromCache = Parameters.CanGetFromCache(session); + CanPutToCache = Parameters.CanPutToCache(session); } /// @@ -136,6 +137,8 @@ public virtual void Init(ISessionImplementor session) Session = session; _queryInfos = GetQueryInformation(session); + // Cache and readonly parameters are the same for all translators + _cacheMode = _queryInfos.First().Parameters.CacheMode; var count = _queryInfos.Count; _subselectResultKeys = new List[count]; @@ -171,80 +174,83 @@ public int ProcessResultsSet(DbDataReader reader) var dialect = Session.Factory.Dialect; var hydratedObjects = new List[_queryInfos.Count]; - var rowCount = 0; - for (var i = 0; i < _queryInfos.Count; i++) + using (Session.SwitchCacheMode(_cacheMode)) { - var queryInfo = _queryInfos[i]; - var loader = queryInfo.Loader; - var queryParameters = queryInfo.Parameters; - - //Skip processing for items already loaded from cache - if (queryInfo.IsResultFromCache) + var rowCount = 0; + for (var i = 0; i < _queryInfos.Count; i++) { - continue; - } + var queryInfo = _queryInfos[i]; + var loader = queryInfo.Loader; + var queryParameters = queryInfo.Parameters; - var entitySpan = loader.EntityPersisters.Length; - hydratedObjects[i] = entitySpan == 0 ? null : new List(entitySpan); - var keys = new EntityKey[entitySpan]; + //Skip processing for items already loaded from cache + if (queryInfo.IsResultFromCache) + { + continue; + } - var selection = queryParameters.RowSelection; - var createSubselects = loader.IsSubselectLoadingEnabled; + var entitySpan = loader.EntityPersisters.Length; + hydratedObjects[i] = entitySpan == 0 ? null : new List(entitySpan); + var keys = new EntityKey[entitySpan]; - _subselectResultKeys[i] = createSubselects ? new List() : null; - var maxRows = Loader.Loader.HasMaxRows(selection) ? selection.MaxRows : int.MaxValue; - var advanceSelection = !dialect.SupportsLimitOffset || !loader.UseLimit(selection, dialect); + var selection = queryParameters.RowSelection; + var createSubselects = loader.IsSubselectLoadingEnabled; - if (advanceSelection) - { - Loader.Loader.Advance(reader, selection); - } + _subselectResultKeys[i] = createSubselects ? new List() : null; + var maxRows = Loader.Loader.HasMaxRows(selection) ? selection.MaxRows : int.MaxValue; + var advanceSelection = !dialect.SupportsLimitOffset || !loader.UseLimit(selection, dialect); - var forcedResultTransformer = queryInfo.CacheKey?.ResultTransformer; - if (queryParameters.HasAutoDiscoverScalarTypes) - { - loader.AutoDiscoverTypes(reader, queryParameters, forcedResultTransformer); - } + if (advanceSelection) + { + Loader.Loader.Advance(reader, selection); + } + + var forcedResultTransformer = queryInfo.CacheKey?.ResultTransformer; + if (queryParameters.HasAutoDiscoverScalarTypes) + { + loader.AutoDiscoverTypes(reader, queryParameters, forcedResultTransformer); + } - var lockModeArray = loader.GetLockModes(queryParameters.LockModes); - var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); - var tmpResults = new List(); + var lockModeArray = loader.GetLockModes(queryParameters.LockModes); + var optionalObjectKey = Loader.Loader.GetOptionalObjectKey(queryParameters, Session); + var tmpResults = new List(); - for (var count = 0; count < maxRows && reader.Read(); count++) - { - rowCount++; - - var o = - loader.GetRowFromResultSet( - reader, - Session, - queryParameters, - lockModeArray, - optionalObjectKey, - hydratedObjects[i], - keys, - true, - forcedResultTransformer - ); - if (loader.IsSubselectLoadingEnabled) + for (var count = 0; count < maxRows && reader.Read(); count++) { - _subselectResultKeys[i].Add(keys); - keys = new EntityKey[entitySpan]; //can't reuse in this case + rowCount++; + + var o = + loader.GetRowFromResultSet( + reader, + Session, + queryParameters, + lockModeArray, + optionalObjectKey, + hydratedObjects[i], + keys, + true, + forcedResultTransformer + ); + if (loader.IsSubselectLoadingEnabled) + { + _subselectResultKeys[i].Add(keys); + keys = new EntityKey[entitySpan]; //can't reuse in this case + } + + tmpResults.Add(o); } - tmpResults.Add(o); + queryInfo.Result = tmpResults; + if (queryInfo.CanPutToCache) + queryInfo.ResultToCache = tmpResults; + + reader.NextResult(); } - queryInfo.Result = tmpResults; - if (queryInfo.CanPutToCache) - queryInfo.ResultToCache = tmpResults; + InitializeEntitiesAndCollections(reader, hydratedObjects); - reader.NextResult(); + return rowCount; } - - InitializeEntitiesAndCollections(reader, hydratedObjects); - - return rowCount; } ///