diff --git a/doc/reference/modules/configuration.xml b/doc/reference/modules/configuration.xml
index 9a0a6df137a..57f6f8b1e3e 100644
--- a/doc/reference/modules/configuration.xml
+++ b/doc/reference/modules/configuration.xml
@@ -722,6 +722,24 @@ var session = sessions.OpenSession(conn);
+
+
+ query.throw_never_cached
+
+
+ Should queries set as cacheable raise an error if they reference an entity using the cache
+ (the default is enabled).
+
+ eg.
+ true | false
+
+
+ Disabling this setting causes NHibernate to ignore the caching of such queries without
+ raising an error. Furthermore NHibernate will log a warning on cacheable queries
+ referencing an entity using the never cache strategy.
+
+
+
query.factory_class
diff --git a/doc/reference/modules/performance.xml b/doc/reference/modules/performance.xml
index 1cd041b08af..f44a5f6a9f8 100644
--- a/doc/reference/modules/performance.xml
+++ b/doc/reference/modules/performance.xml
@@ -733,8 +733,8 @@ using(var iter = session
- ]]>
@@ -742,8 +742,9 @@ using(var iter = session
usage specifies the caching strategy:
read-write,
- nonstrict-read-write or
- read-only
+ nonstrict-read-write,
+ read-only or
+ never
@@ -824,6 +825,26 @@ using(var iter = session
+
+ Strategy: never
+
+
+ By default, without a cache configuration, entities are not cacheable. But they may still be referenced
+ in cacheable queries, which results will then be cached according to the values these non cacheable
+ entities have. So, their data may be indirectly cached through cacheable queries.
+
+
+
+ By using the cache strategy never, such indirect caching of these entities data will
+ be forbidden by NHibernate. Setting as cacheable a query referencing entities with strategy
+ never will be treated as an error by default. Alternatively, the
+ query.throw_never_cached setting can be
+ set to false: instead of raising an error, it will disable the query cache on such
+ queries, and log a warning.
+
+
+
+
The following table shows which providers are compatible with which concurrency strategies.
diff --git a/src/NHibernate.Test/App.config b/src/NHibernate.Test/App.config
index 40cf748a495..6bbf371c023 100644
--- a/src/NHibernate.Test/App.config
+++ b/src/NHibernate.Test/App.config
@@ -38,6 +38,7 @@
true
false
+ true
true 1, false 0, yes 'Y', no 'N'
10
diff --git a/src/NHibernate.Test/Async/SecondLevelCacheTest/NeverCachedEntityTests.cs b/src/NHibernate.Test/Async/SecondLevelCacheTest/NeverCachedEntityTests.cs
new file mode 100644
index 00000000000..bfcac5833ed
--- /dev/null
+++ b/src/NHibernate.Test/Async/SecondLevelCacheTest/NeverCachedEntityTests.cs
@@ -0,0 +1,225 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using NHibernate.Cache;
+using NHibernate.Cfg;
+using NHibernate.Impl;
+using NHibernate.Linq;
+using NHibernate.Test.SecondLevelCacheTests;
+using NSubstitute;
+using NUnit.Framework;
+
+namespace NHibernate.Test.SecondLevelCacheTest
+{
+ using System.Threading.Tasks;
+ using System.Threading;
+ [TestFixture]
+ public class NeverCachedEntityTestsAsync : TestCase
+ {
+ protected override string CacheConcurrencyStrategy => null;
+ protected override string MappingsAssembly => "NHibernate.Test";
+
+ protected override string[] Mappings => new[] { "SecondLevelCacheTest.Item.hbm.xml" };
+
+ protected override void Configure(Configuration configuration)
+ {
+ configuration.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName);
+ configuration.SetProperty(Environment.UseQueryCache, "true");
+ }
+
+ [Test]
+ public async Task NeverInvalidateEntitiesAsync()
+ {
+ var debugSessionFactory = (DebugSessionFactory) Sfi;
+
+ var cache = Substitute.For(Sfi.Settings, new Dictionary());
+
+ var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField(
+ "updateTimestampsCache",
+ BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+
+ updateTimestampsCacheField.SetValue(debugSessionFactory.ActualFactory, cache);
+
+ //"Received" assertions can not be used since the collection is reused and cleared between calls.
+ //The received args are cloned and stored
+ var preInvalidations = new List>();
+ var invalidations = new List>();
+
+ await (cache.PreInvalidateAsync(Arg.Do>(x => preInvalidations.Add(x.ToList())), CancellationToken.None));
+ await (cache.InvalidateAsync(Arg.Do>(x => invalidations.Add(x.ToList())), CancellationToken.None));
+
+ using (var session = OpenSession())
+ {
+ List ids = new List();
+ //Add NeverItem
+ using (var tx = session.BeginTransaction())
+ {
+ foreach (var i in Enumerable.Range(1, 10))
+ {
+ var item = new NeverItem { Name = "Abatay" };
+ item.Childrens.Add(new NeverChildItem()
+ {
+ Name = "Child",
+ Parent = item
+ });
+ await (session.SaveAsync(item));
+ ids.Add(item.Id);
+ }
+
+ await (tx.CommitAsync());
+ }
+
+ //Update NeverItem
+ using (var tx = session.BeginTransaction())
+ {
+ foreach (var i in ids)
+ {
+ var item = await (session.GetAsync(i));
+ item.Name = item.Id.ToString();
+ }
+
+ await (tx.CommitAsync());
+ }
+
+ //Delete NeverItem
+ using (var tx = session.BeginTransaction())
+ {
+ foreach (var i in ids)
+ {
+ var item = await (session.GetAsync(i));
+ await (session.DeleteAsync(item));
+ }
+
+ await (tx.CommitAsync());
+ }
+
+ //Update NeverItem using HQL
+ using (var tx = session.BeginTransaction())
+ {
+ await (session.CreateQuery("UPDATE NeverItem SET Name='Test'").ExecuteUpdateAsync());
+
+ await (tx.CommitAsync());
+ }
+
+ //Update NeverItem using LINQ
+ using (var tx = session.BeginTransaction())
+ {
+ await (session.Query()
+ .UpdateBuilder()
+ .Set(x => x.Name, "Test")
+ .UpdateAsync());
+
+ await (tx.CommitAsync());
+ }
+ }
+
+ //Should receive none preinvalidation when Cache is configured as never
+ Assert.That(preInvalidations, Has.Count.EqualTo(0));
+
+ //Should receive none invalidation when Cache is configured as never
+ Assert.That(invalidations, Has.Count.EqualTo(0));
+ }
+
+ [Test]
+ public async Task QueryCache_ThrowsExceptionAsync()
+ {
+ using (var session = OpenSession())
+ {
+ //Linq
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.ThrowsAsync(() => session
+ .Query().WithOptions(x => x.SetCacheable(true)).ToListAsync());
+
+ await (tx.CommitAsync());
+ }
+
+ //Linq Multiple with error message we will quarantied that gets 2 class in error message
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.ThrowsAsync(() => session
+ .Query().Where(x => x.Childrens.Any())
+ .WithOptions(x => x.SetCacheable(true))
+ .ToListAsync(),
+ $"Never cached entity:{string.Join(", ", typeof(NeverItem).FullName, typeof(NeverChildItem).FullName)} cannot be used in cacheable query");
+
+ await (tx.CommitAsync());
+ }
+
+ //Hql
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.ThrowsAsync(() => session
+ .CreateQuery("from NeverItem").SetCacheable(true).ListAsync());
+
+ await (tx.CommitAsync());
+ }
+
+ //ICriteria
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.ThrowsAsync(() => session
+ .CreateCriteria()
+ .SetCacheable(true)
+ .ListAsync());
+
+ await (tx.CommitAsync());
+ }
+
+ //Native Sql
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.ThrowsAsync(() => session
+ .CreateSQLQuery("select * from NeverItem")
+ .AddSynchronizedQuerySpace("NeverItem")
+ .SetCacheable(true)
+ .ListAsync());
+
+ await (tx.CommitAsync());
+ }
+ }
+ }
+
+ [Test]
+ public async Task ShouldAutoFlushAsync()
+ {
+ using (var session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var e1 = new NeverItem { Name = "Abatay" };
+ e1.Childrens.Add(new NeverChildItem()
+ {
+ Name = "Child",
+ Parent = e1
+ });
+ await (session.SaveAsync(e1));
+
+ var result = await ((from e in session.Query()
+ where e.Name == "Abatay"
+ select e).ToListAsync());
+
+ Assert.That(result.Count, Is.EqualTo(1));
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var s = OpenSession())
+ using (var tx = s.BeginTransaction())
+ {
+ s.Delete("from NeverItem");
+ tx.Commit();
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/CfgTest/EntityCacheUsageParserFixture.cs b/src/NHibernate.Test/CfgTest/EntityCacheUsageParserFixture.cs
index 5470a59b1b6..5a12f674afd 100644
--- a/src/NHibernate.Test/CfgTest/EntityCacheUsageParserFixture.cs
+++ b/src/NHibernate.Test/CfgTest/EntityCacheUsageParserFixture.cs
@@ -13,6 +13,7 @@ public void CovertToString()
Assert.That(EntityCacheUsageParser.ToString(EntityCacheUsage.ReadWrite), Is.EqualTo("read-write"));
Assert.That(EntityCacheUsageParser.ToString(EntityCacheUsage.NonStrictReadWrite), Is.EqualTo("nonstrict-read-write"));
Assert.That(EntityCacheUsageParser.ToString(EntityCacheUsage.Transactional), Is.EqualTo("transactional"));
+ Assert.That(EntityCacheUsageParser.ToString(EntityCacheUsage.Never), Is.EqualTo("never"));
}
[Test]
@@ -22,6 +23,7 @@ public void Parse()
Assert.That(EntityCacheUsageParser.Parse("read-write"), Is.EqualTo(EntityCacheUsage.ReadWrite));
Assert.That(EntityCacheUsageParser.Parse("nonstrict-read-write"), Is.EqualTo(EntityCacheUsage.NonStrictReadWrite));
Assert.That(EntityCacheUsageParser.Parse("transactional"), Is.EqualTo(EntityCacheUsage.Transactional));
+ Assert.That(EntityCacheUsageParser.Parse("never"), Is.EqualTo(EntityCacheUsage.Never));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/NHibernate.Test/DebugSessionFactory.cs b/src/NHibernate.Test/DebugSessionFactory.cs
index 7643924119f..7eb551bb83f 100644
--- a/src/NHibernate.Test/DebugSessionFactory.cs
+++ b/src/NHibernate.Test/DebugSessionFactory.cs
@@ -316,6 +316,16 @@ ICollectionPersister ISessionFactoryImplementor.GetCollectionPersister(string ro
return ActualFactory.GetCollectionPersister(role);
}
+ public ISet GetEntityPersisters(ISet spaces)
+ {
+ return ActualFactory.GetEntityPersisters(spaces);
+ }
+
+ public ISet GetCollectionPersisters(ISet spaces)
+ {
+ return ActualFactory.GetCollectionPersisters(spaces);
+ }
+
IType[] ISessionFactoryImplementor.GetReturnTypes(string queryString)
{
return ActualFactory.GetReturnTypes(queryString);
diff --git a/src/NHibernate.Test/SecondLevelCacheTest/Item.hbm.xml b/src/NHibernate.Test/SecondLevelCacheTest/Item.hbm.xml
index 1012fc499cb..f21a1de71ba 100644
--- a/src/NHibernate.Test/SecondLevelCacheTest/Item.hbm.xml
+++ b/src/NHibernate.Test/SecondLevelCacheTest/Item.hbm.xml
@@ -26,8 +26,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
select ai.Name, count(*) from AnotherItem ai group by ai.Name
-
\ No newline at end of file
+
diff --git a/src/NHibernate.Test/SecondLevelCacheTest/NeverCachedEntityTests.cs b/src/NHibernate.Test/SecondLevelCacheTest/NeverCachedEntityTests.cs
new file mode 100644
index 00000000000..7c0ba5f3602
--- /dev/null
+++ b/src/NHibernate.Test/SecondLevelCacheTest/NeverCachedEntityTests.cs
@@ -0,0 +1,213 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using NHibernate.Cache;
+using NHibernate.Cfg;
+using NHibernate.Impl;
+using NHibernate.Linq;
+using NHibernate.Test.SecondLevelCacheTests;
+using NSubstitute;
+using NUnit.Framework;
+
+namespace NHibernate.Test.SecondLevelCacheTest
+{
+ [TestFixture]
+ public class NeverCachedEntityTests : TestCase
+ {
+ protected override string CacheConcurrencyStrategy => null;
+ protected override string MappingsAssembly => "NHibernate.Test";
+
+ protected override string[] Mappings => new[] { "SecondLevelCacheTest.Item.hbm.xml" };
+
+ protected override void Configure(Configuration configuration)
+ {
+ configuration.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName);
+ configuration.SetProperty(Environment.UseQueryCache, "true");
+ }
+
+ [Test]
+ public void NeverInvalidateEntities()
+ {
+ var debugSessionFactory = (DebugSessionFactory) Sfi;
+
+ var cache = Substitute.For(Sfi.Settings, new Dictionary());
+
+ var updateTimestampsCacheField = typeof(SessionFactoryImpl).GetField(
+ "updateTimestampsCache",
+ BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+
+ updateTimestampsCacheField.SetValue(debugSessionFactory.ActualFactory, cache);
+
+ //"Received" assertions can not be used since the collection is reused and cleared between calls.
+ //The received args are cloned and stored
+ var preInvalidations = new List>();
+ var invalidations = new List>();
+
+ cache.PreInvalidate(Arg.Do>(x => preInvalidations.Add(x.ToList())));
+ cache.Invalidate(Arg.Do>(x => invalidations.Add(x.ToList())));
+
+ using (var session = OpenSession())
+ {
+ List ids = new List();
+ //Add NeverItem
+ using (var tx = session.BeginTransaction())
+ {
+ foreach (var i in Enumerable.Range(1, 10))
+ {
+ var item = new NeverItem { Name = "Abatay" };
+ item.Childrens.Add(new NeverChildItem()
+ {
+ Name = "Child",
+ Parent = item
+ });
+ session.Save(item);
+ ids.Add(item.Id);
+ }
+
+ tx.Commit();
+ }
+
+ //Update NeverItem
+ using (var tx = session.BeginTransaction())
+ {
+ foreach (var i in ids)
+ {
+ var item = session.Get(i);
+ item.Name = item.Id.ToString();
+ }
+
+ tx.Commit();
+ }
+
+ //Delete NeverItem
+ using (var tx = session.BeginTransaction())
+ {
+ foreach (var i in ids)
+ {
+ var item = session.Get(i);
+ session.Delete(item);
+ }
+
+ tx.Commit();
+ }
+
+ //Update NeverItem using HQL
+ using (var tx = session.BeginTransaction())
+ {
+ session.CreateQuery("UPDATE NeverItem SET Name='Test'").ExecuteUpdate();
+
+ tx.Commit();
+ }
+
+ //Update NeverItem using LINQ
+ using (var tx = session.BeginTransaction())
+ {
+ session.Query()
+ .UpdateBuilder()
+ .Set(x => x.Name, "Test")
+ .Update();
+
+ tx.Commit();
+ }
+ }
+
+ //Should receive none preinvalidation when Cache is configured as never
+ Assert.That(preInvalidations, Has.Count.EqualTo(0));
+
+ //Should receive none invalidation when Cache is configured as never
+ Assert.That(invalidations, Has.Count.EqualTo(0));
+ }
+
+ [Test]
+ public void QueryCache_ThrowsException()
+ {
+ using (var session = OpenSession())
+ {
+ //Linq
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.Throws(() => session
+ .Query().WithOptions(x => x.SetCacheable(true)).ToList());
+
+ tx.Commit();
+ }
+
+ //Linq Multiple with error message we will quarantied that gets 2 class in error message
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.Throws(() => session
+ .Query().Where(x => x.Childrens.Any())
+ .WithOptions(x => x.SetCacheable(true))
+ .ToList(),
+ $"Never cached entity:{string.Join(", ", typeof(NeverItem).FullName, typeof(NeverChildItem).FullName)} cannot be used in cacheable query");
+
+ tx.Commit();
+ }
+
+ //Hql
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.Throws(() => session
+ .CreateQuery("from NeverItem").SetCacheable(true).List());
+
+ tx.Commit();
+ }
+
+ //ICriteria
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.Throws(() => session
+ .CreateCriteria()
+ .SetCacheable(true)
+ .List());
+
+ tx.Commit();
+ }
+
+ //Native Sql
+ using (var tx = session.BeginTransaction())
+ {
+ Assert.Throws(() => session
+ .CreateSQLQuery("select * from NeverItem")
+ .AddSynchronizedQuerySpace("NeverItem")
+ .SetCacheable(true)
+ .List());
+
+ tx.Commit();
+ }
+ }
+ }
+
+ [Test]
+ public void ShouldAutoFlush()
+ {
+ using (var session = OpenSession())
+ using (session.BeginTransaction())
+ {
+ var e1 = new NeverItem { Name = "Abatay" };
+ e1.Childrens.Add(new NeverChildItem()
+ {
+ Name = "Child",
+ Parent = e1
+ });
+ session.Save(e1);
+
+ var result = (from e in session.Query()
+ where e.Name == "Abatay"
+ select e).ToList();
+
+ Assert.That(result.Count, Is.EqualTo(1));
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var s = OpenSession())
+ using (var tx = s.BeginTransaction())
+ {
+ s.Delete("from NeverItem");
+ tx.Commit();
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/SecondLevelCacheTest/NeverItem.cs b/src/NHibernate.Test/SecondLevelCacheTest/NeverItem.cs
new file mode 100644
index 00000000000..69ad34f2c24
--- /dev/null
+++ b/src/NHibernate.Test/SecondLevelCacheTest/NeverItem.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+namespace NHibernate.Test.SecondLevelCacheTests
+{
+ public class NeverItem
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual string Description { get; set; }
+
+ public virtual IList Childrens { get; set; } = new List();
+ }
+
+ public class NeverChildItem
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+
+ public virtual NeverItem Parent { get; set; }
+ }
+}
diff --git a/src/NHibernate/Action/BulkOperationCleanupAction.cs b/src/NHibernate/Action/BulkOperationCleanupAction.cs
index edd5b8f20bd..5308d2834d7 100644
--- a/src/NHibernate/Action/BulkOperationCleanupAction.cs
+++ b/src/NHibernate/Action/BulkOperationCleanupAction.cs
@@ -4,7 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using NHibernate.Engine;
-using NHibernate.Metadata;
+using NHibernate.Persister;
using NHibernate.Persister.Entity;
using IQueryable = NHibernate.Persister.Entity.IQueryable;
@@ -14,34 +14,45 @@ namespace NHibernate.Action
/// Implementation of BulkOperationCleanupAction.
///
[Serializable]
- public partial class BulkOperationCleanupAction : IAsyncExecutable, IAfterTransactionCompletionProcess
+ public partial class BulkOperationCleanupAction : IAsyncExecutable, IAfterTransactionCompletionProcess, ICacheableExecutable
{
private readonly ISessionImplementor session;
private readonly HashSet affectedEntityNames = new HashSet();
private readonly HashSet affectedCollectionRoles = new HashSet();
- private readonly List spaces;
+ private readonly string[] spaces;
+ private readonly string[] queryCacheSpaces;
public BulkOperationCleanupAction(ISessionImplementor session, IQueryable[] affectedQueryables)
{
this.session = session;
- List tmpSpaces = new List();
- for (int i = 0; i < affectedQueryables.Length; i++)
+ var affectedSpaces = new HashSet();
+ var affectedQueryCacheSpaces = new HashSet();
+ foreach (var affectedQueryable in affectedQueryables)
{
- if (affectedQueryables[i].HasCache)
+ if (affectedQueryable.HasCache)
{
- affectedEntityNames.Add(affectedQueryables[i].EntityName);
+ affectedEntityNames.Add(affectedQueryable.EntityName);
}
- ISet roles = session.Factory.GetCollectionRolesByEntityParticipant(affectedQueryables[i].EntityName);
+ ISet roles = session.Factory.GetCollectionRolesByEntityParticipant(affectedQueryable.EntityName);
if (roles != null)
{
affectedCollectionRoles.UnionWith(roles);
}
- for (int y = 0; y < affectedQueryables[i].QuerySpaces.Length; y++)
+
+ // 6.0 TODO: Use IPersister.SupportsQueryCache property once IPersister's todo is done.
+ var supportsQuerySpace = affectedQueryable.SupportsQueryCache();
+ foreach (var querySpace in affectedQueryable.QuerySpaces)
{
- tmpSpaces.Add(affectedQueryables[i].QuerySpaces[y]);
+ affectedSpaces.Add(querySpace);
+ if (supportsQuerySpace)
+ {
+ affectedQueryCacheSpaces.Add(querySpace);
+ }
}
}
- spaces = new List(tmpSpaces);
+
+ spaces = affectedSpaces.ToArray();
+ queryCacheSpaces = affectedQueryCacheSpaces.ToArray();
}
///
@@ -52,50 +63,45 @@ public BulkOperationCleanupAction(ISessionImplementor session, ISet quer
//from H3.2 TODO: cache the autodetected information and pass it in instead.
this.session = session;
- ISet tmpSpaces = new HashSet(querySpaces);
- ISessionFactoryImplementor factory = session.Factory;
- IDictionary acmd = factory.GetAllClassMetadata();
- foreach (KeyValuePair entry in acmd)
+ var affectedSpaces = new HashSet(querySpaces);
+ var affectedQueryCacheSpaces = new HashSet();
+
+ foreach (var persister in session.Factory.GetEntityPersisters(querySpaces))
{
- string entityName = entry.Key;
- IEntityPersister persister = factory.GetEntityPersister(entityName);
- string[] entitySpaces = persister.QuerySpaces;
+ if (persister.HasCache)
+ {
+ affectedEntityNames.Add(persister.EntityName);
+ }
+ ISet roles = session.Factory.GetCollectionRolesByEntityParticipant(persister.EntityName);
+ if (roles != null)
+ {
+ affectedCollectionRoles.UnionWith(roles);
+ }
- if (AffectedEntity(querySpaces, entitySpaces))
+ // 6.0 TODO: Use IPersister.SupportsQueryCache property once IPersister's todo is done.
+ var supportsQuerySpace = persister.SupportsQueryCache();
+ foreach (var querySpace in persister.QuerySpaces)
{
- if (persister.HasCache)
+ affectedSpaces.Add(querySpace);
+ if (supportsQuerySpace)
{
- affectedEntityNames.Add(persister.EntityName);
- }
- ISet roles = session.Factory.GetCollectionRolesByEntityParticipant(persister.EntityName);
- if (roles != null)
- {
- affectedCollectionRoles.UnionWith(roles);
- }
- for (int y = 0; y < entitySpaces.Length; y++)
- {
- tmpSpaces.Add(entitySpaces[y]);
+ affectedQueryCacheSpaces.Add(querySpace);
}
}
}
- spaces = new List(tmpSpaces);
- }
-
- private bool AffectedEntity(ISet querySpaces, string[] entitySpaces)
- {
- if (querySpaces == null || (querySpaces.Count == 0))
- {
- return true;
- }
- return entitySpaces.Any(querySpaces.Contains);
+ spaces = affectedSpaces.ToArray();
+ queryCacheSpaces = affectedQueryCacheSpaces.ToArray();
}
#region IExecutable Members
+ ///
+ public string[] QueryCacheSpaces => queryCacheSpaces;
+
public string[] PropertySpaces
{
- get { return spaces.ToArray(); }
+ get { return spaces; }
}
public void BeforeExecutions()
diff --git a/src/NHibernate/Action/CollectionAction.cs b/src/NHibernate/Action/CollectionAction.cs
index e676e7d4f18..4071211cdb8 100644
--- a/src/NHibernate/Action/CollectionAction.cs
+++ b/src/NHibernate/Action/CollectionAction.cs
@@ -5,7 +5,9 @@
using NHibernate.Collection;
using NHibernate.Engine;
using NHibernate.Impl;
+using NHibernate.Persister;
using NHibernate.Persister.Collection;
+using NHibernate.Persister.Entity;
using NHibernate.Util;
namespace NHibernate.Action
@@ -14,7 +16,12 @@ namespace NHibernate.Action
/// Any action relating to insert/update/delete of a collection
///
[Serializable]
- public abstract partial class CollectionAction : IAsyncExecutable, IComparable, IDeserializationCallback, IAfterTransactionCompletionProcess
+ public abstract partial class CollectionAction :
+ IAsyncExecutable,
+ IComparable,
+ IDeserializationCallback,
+ IAfterTransactionCompletionProcess,
+ ICacheableExecutable
{
private readonly object key;
[NonSerialized] private ICollectionPersister persister;
@@ -79,6 +86,15 @@ protected internal ISessionImplementor Session
#region IExecutable Members
+ public string[] QueryCacheSpaces
+ {
+ get
+ {
+ // 6.0 TODO: Use IPersister.SupportsQueryCache property once IPersister's todo is done.
+ return persister.SupportsQueryCache() ? persister.CollectionSpaces : null;
+ }
+ }
+
///
/// What spaces (tables) are affected by this action?
///
diff --git a/src/NHibernate/Action/EntityAction.cs b/src/NHibernate/Action/EntityAction.cs
index 70b2b1e60c2..07bcf94f097 100644
--- a/src/NHibernate/Action/EntityAction.cs
+++ b/src/NHibernate/Action/EntityAction.cs
@@ -5,6 +5,7 @@
using NHibernate.Persister.Entity;
using NHibernate.Util;
using NHibernate.Impl;
+using NHibernate.Persister;
namespace NHibernate.Action
{
@@ -18,7 +19,8 @@ public abstract partial class EntityAction :
IBeforeTransactionCompletionProcess,
IAfterTransactionCompletionProcess,
IComparable,
- IDeserializationCallback
+ IDeserializationCallback,
+ ICacheableExecutable
{
private readonly string entityName;
private readonly object id;
@@ -95,6 +97,15 @@ public IEntityPersister Persister
#region IExecutable Members
+ public string[] QueryCacheSpaces
+ {
+ get
+ {
+ // 6.0 TODO: Use IPersister.SupportsQueryCache property once IPersister's todo is done.
+ return persister.SupportsQueryCache() ? persister.PropertySpaces : null;
+ }
+ }
+
public string[] PropertySpaces
{
get { return persister.PropertySpaces; }
diff --git a/src/NHibernate/Action/ICacheableExecutable.cs b/src/NHibernate/Action/ICacheableExecutable.cs
new file mode 100644
index 00000000000..6f20df92f18
--- /dev/null
+++ b/src/NHibernate/Action/ICacheableExecutable.cs
@@ -0,0 +1,11 @@
+namespace NHibernate.Action
+{
+ //6.0 TODO: Merge to IExecutable
+ public interface ICacheableExecutable : IExecutable
+ {
+ ///
+ /// The query cache spaces (tables) which are affected by this action.
+ ///
+ string[] QueryCacheSpaces { get; }
+ }
+}
diff --git a/src/NHibernate/Async/Action/BulkOperationCleanupAction.cs b/src/NHibernate/Async/Action/BulkOperationCleanupAction.cs
index 18d4553f24f..8c5201de202 100644
--- a/src/NHibernate/Async/Action/BulkOperationCleanupAction.cs
+++ b/src/NHibernate/Async/Action/BulkOperationCleanupAction.cs
@@ -14,13 +14,13 @@
using System.Threading;
using System.Threading.Tasks;
using NHibernate.Engine;
-using NHibernate.Metadata;
+using NHibernate.Persister;
using NHibernate.Persister.Entity;
using IQueryable = NHibernate.Persister.Entity.IQueryable;
namespace NHibernate.Action
{
- public partial class BulkOperationCleanupAction : IAsyncExecutable, IAfterTransactionCompletionProcess
+ public partial class BulkOperationCleanupAction : IAsyncExecutable, IAfterTransactionCompletionProcess, ICacheableExecutable
{
#region IExecutable Members
diff --git a/src/NHibernate/Async/Action/CollectionAction.cs b/src/NHibernate/Async/Action/CollectionAction.cs
index 237d2c34afe..3c98605cda5 100644
--- a/src/NHibernate/Async/Action/CollectionAction.cs
+++ b/src/NHibernate/Async/Action/CollectionAction.cs
@@ -15,14 +15,21 @@
using NHibernate.Collection;
using NHibernate.Engine;
using NHibernate.Impl;
+using NHibernate.Persister;
using NHibernate.Persister.Collection;
+using NHibernate.Persister.Entity;
using NHibernate.Util;
namespace NHibernate.Action
{
using System.Threading.Tasks;
using System.Threading;
- public abstract partial class CollectionAction : IAsyncExecutable, IComparable, IDeserializationCallback, IAfterTransactionCompletionProcess
+ public abstract partial class CollectionAction :
+ IAsyncExecutable,
+ IComparable,
+ IDeserializationCallback,
+ IAfterTransactionCompletionProcess,
+ ICacheableExecutable
{
protected async Task