From 6631968bfa2e61b8909b9c93540634fe04e43a37 Mon Sep 17 00:00:00 2001 From: Christoph Watzl Date: Tue, 21 Jan 2025 19:26:24 +0100 Subject: [PATCH 1/5] Provide test case for #3643 --- .../NHSpecificTest/GH3643/Entity.cs | 27 ++++ .../NHSpecificTest/GH3643/FixtureByCode.cs | 118 ++++++++++++++++++ src/NHibernate.sln.DotSettings | 4 + 3 files changed, 149 insertions(+) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs diff --git a/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs new file mode 100644 index 00000000000..60a3de3c863 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +// ReSharper disable CollectionNeverUpdated.Local +// ReSharper disable UnassignedGetOnlyAutoProperty + +namespace NHibernate.Test.NHSpecificTest.GH3643 +{ + class Entity + { + private readonly ICollection _children = new List(); + public virtual EntityId Id { get; protected set; } + public virtual IEnumerable Children => _children.AsEnumerable(); + } + + class ChildEntity + { + public virtual int Id { get; protected set; } + } + + enum EntityId + { + Id1, + Id2 + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs new file mode 100644 index 00000000000..c2694e3b637 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs @@ -0,0 +1,118 @@ +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3643 +{ + [TestFixture] + public class FixtureByCode : TestCaseMappingByCode + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.UseQueryCache, "true"); + configuration.SetProperty(Environment.GenerateStatistics, "true"); + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Bag( + x => x.Children, + m => + { + m.Access(Accessor.Field); + m.Key(k => k.Column("EntityId")); + }, + r => r.OneToMany()); + + rc.Cache( + cm => + { + cm.Include(CacheInclude.All); + cm.Usage(CacheUsage.ReadWrite); + }); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Cache( + cm => + { + cm.Include(CacheInclude.All); + cm.Usage(CacheUsage.ReadWrite); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery( + "INSERT INTO Entity (Id) VALUES (0)" + ).ExecuteUpdate(); + + session.CreateSQLQuery( + "INSERT INTO ChildEntity (Id, EntityId) VALUES (0, 0)" + ).ExecuteUpdate(); + + session.CreateSQLQuery( + "INSERT INTO ChildEntity (Id, EntityId) VALUES (1, 0)" + ).ExecuteUpdate(); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery("DELETE FROM ChildEntity").ExecuteUpdate(); + session.CreateSQLQuery("DELETE FROM Entity").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void LoadsEntityWithEnumIdAndChildrenUsingQueryCache() + { + LoadEntityWithQueryCache(); // warm up cache + + var entity = LoadEntityWithQueryCache(); + + Assert.That(entity.Children.Count(), Is.EqualTo(2)); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + private Entity LoadEntityWithQueryCache() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var entity = session + .Query() + .FetchMany(x => x.Children) + .WithOptions(opt => opt.SetCacheable(true)) + .ToList()[0]; + + transaction.Commit(); + return entity; + } + } +} diff --git a/src/NHibernate.sln.DotSettings b/src/NHibernate.sln.DotSettings index d78384192e9..b77ef433c2a 100644 --- a/src/NHibernate.sln.DotSettings +++ b/src/NHibernate.sln.DotSettings @@ -21,6 +21,9 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True True @@ -29,6 +32,7 @@ True True True + True True True True From 3b29f38ab01a8980336d5638c3010d91739317a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jan 2025 14:43:57 +0000 Subject: [PATCH 2/5] Generate async files --- .../NHSpecificTest/GH3643/FixtureByCode.cs | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs new file mode 100644 index 00000000000..7f40824898d --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3643 +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public class FixtureByCodeAsync : TestCaseMappingByCode + { + protected override void Configure(Configuration configuration) + { + configuration.SetProperty(Environment.UseQueryCache, "true"); + configuration.SetProperty(Environment.GenerateStatistics, "true"); + } + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Bag( + x => x.Children, + m => + { + m.Access(Accessor.Field); + m.Key(k => k.Column("EntityId")); + }, + r => r.OneToMany()); + + rc.Cache( + cm => + { + cm.Include(CacheInclude.All); + cm.Usage(CacheUsage.ReadWrite); + }); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id); + rc.Cache( + cm => + { + cm.Include(CacheInclude.All); + cm.Usage(CacheUsage.ReadWrite); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery( + "INSERT INTO Entity (Id) VALUES (0)" + ).ExecuteUpdate(); + + session.CreateSQLQuery( + "INSERT INTO ChildEntity (Id, EntityId) VALUES (0, 0)" + ).ExecuteUpdate(); + + session.CreateSQLQuery( + "INSERT INTO ChildEntity (Id, EntityId) VALUES (1, 0)" + ).ExecuteUpdate(); + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + + session.CreateSQLQuery("DELETE FROM ChildEntity").ExecuteUpdate(); + session.CreateSQLQuery("DELETE FROM Entity").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task LoadsEntityWithEnumIdAndChildrenUsingQueryCacheAsync() + { + await (LoadEntityWithQueryCacheAsync()); // warm up cache + + var entity = await (LoadEntityWithQueryCacheAsync()); + + Assert.That(entity.Children.Count(), Is.EqualTo(2)); + + Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count"); + Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count"); + Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count"); + } + + private async Task LoadEntityWithQueryCacheAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var entity = (await (session + .Query() + .FetchMany(x => x.Children) + .WithOptions(opt => opt.SetCacheable(true)) + .ToListAsync(cancellationToken)))[0]; + + await (transaction.CommitAsync(cancellationToken)); + return entity; + } + } +} From 08c6a3fb0c0658f66d5e7836d4cb5a6c957d8e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sat, 23 Aug 2025 12:10:38 +0200 Subject: [PATCH 3/5] Fix collection cache lookup failure with enum keys fix #3643 --- src/NHibernate/Async/Type/TypeHelper.cs | 1 + src/NHibernate/Type/TypeHelper.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NHibernate/Async/Type/TypeHelper.cs b/src/NHibernate/Async/Type/TypeHelper.cs index 3446d115249..0f04ceebb7e 100644 --- a/src/NHibernate/Async/Type/TypeHelper.cs +++ b/src/NHibernate/Async/Type/TypeHelper.cs @@ -125,6 +125,7 @@ internal static async Task InitializeCollectionsAsync( continue; } + value = await (pair.Value.KeyType.AssembleAsync(value, session, null, cancellationToken)).ConfigureAwait(false); var collection = session.PersistenceContext.GetCollection(new CollectionKey(pair.Value, value)); await (collection.ForceInitializationAsync(cancellationToken)).ConfigureAwait(false); assembleRow[pair.Key] = collection; diff --git a/src/NHibernate/Type/TypeHelper.cs b/src/NHibernate/Type/TypeHelper.cs index 1e28e73c8a5..d7278d4a386 100644 --- a/src/NHibernate/Type/TypeHelper.cs +++ b/src/NHibernate/Type/TypeHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using NHibernate.Collection; @@ -133,6 +133,7 @@ internal static void InitializeCollections( continue; } + value = pair.Value.KeyType.Assemble(value, session, null); var collection = session.PersistenceContext.GetCollection(new CollectionKey(pair.Value, value)); collection.ForceInitialization(); assembleRow[pair.Key] = collection; From 96f84a2e946de4856758bd5377561b4c1366fb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sat, 23 Aug 2025 13:28:13 +0200 Subject: [PATCH 4/5] Clean up the test case --- .../NHSpecificTest/GH3643/Entity.cs | 12 ++++---- .../NHSpecificTest/GH3643/FixtureByCode.cs | 28 ++++++++----------- src/NHibernate.sln.DotSettings | 4 --- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs index 60a3de3c863..09f08d8dbad 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3643/Entity.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; // ReSharper disable CollectionNeverUpdated.Local // ReSharper disable UnassignedGetOnlyAutoProperty @@ -9,14 +7,14 @@ namespace NHibernate.Test.NHSpecificTest.GH3643 { class Entity { - private readonly ICollection _children = new List(); - public virtual EntityId Id { get; protected set; } - public virtual IEnumerable Children => _children.AsEnumerable(); + private readonly ICollection _children = []; + public virtual EntityId Id { get; set; } + public virtual ICollection Children => _children; } class ChildEntity { - public virtual int Id { get; protected set; } + public virtual int Id { get; set; } } enum EntityId diff --git a/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs index c2694e3b637..2fc0741b484 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3643/FixtureByCode.cs @@ -30,6 +30,7 @@ protected override HbmMapping GetMappings() { m.Access(Accessor.Field); m.Key(k => k.Column("EntityId")); + m.Cascade(Mapping.ByCode.Cascade.All); }, r => r.OneToMany()); @@ -60,19 +61,12 @@ protected override void OnSetUp() { using var session = OpenSession(); using var transaction = session.BeginTransaction(); - - session.CreateSQLQuery( - "INSERT INTO Entity (Id) VALUES (0)" - ).ExecuteUpdate(); - session.CreateSQLQuery( - "INSERT INTO ChildEntity (Id, EntityId) VALUES (0, 0)" - ).ExecuteUpdate(); + var entity = new Entity { Id = EntityId.Id1 }; + entity.Children.Add(new ChildEntity { Id = 0 }); + entity.Children.Add(new ChildEntity { Id = 1 }); + session.Save(entity); - session.CreateSQLQuery( - "INSERT INTO ChildEntity (Id, EntityId) VALUES (1, 0)" - ).ExecuteUpdate(); - transaction.Commit(); } @@ -81,8 +75,8 @@ protected override void OnTearDown() using var session = OpenSession(); using var transaction = session.BeginTransaction(); - session.CreateSQLQuery("DELETE FROM ChildEntity").ExecuteUpdate(); - session.CreateSQLQuery("DELETE FROM Entity").ExecuteUpdate(); + session.CreateQuery("delete from ChildEntity").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); transaction.Commit(); } @@ -106,10 +100,10 @@ private Entity LoadEntityWithQueryCache() using var session = OpenSession(); using var transaction = session.BeginTransaction(); var entity = session - .Query() - .FetchMany(x => x.Children) - .WithOptions(opt => opt.SetCacheable(true)) - .ToList()[0]; + .Query() + .FetchMany(x => x.Children) + .WithOptions(opt => opt.SetCacheable(true)) + .ToList()[0]; transaction.Commit(); return entity; diff --git a/src/NHibernate.sln.DotSettings b/src/NHibernate.sln.DotSettings index b77ef433c2a..d78384192e9 100644 --- a/src/NHibernate.sln.DotSettings +++ b/src/NHibernate.sln.DotSettings @@ -21,9 +21,6 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> - <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> - <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True True @@ -32,7 +29,6 @@ True True True - True True True True From 360db2907e01ca9519f7e2ca75268112188c1642 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 Aug 2025 11:47:37 +0000 Subject: [PATCH 5/5] Generate async files --- .../NHSpecificTest/GH3643/FixtureByCode.cs | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs index 7f40824898d..71b43faf92e 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3643/FixtureByCode.cs @@ -42,6 +42,7 @@ protected override HbmMapping GetMappings() { m.Access(Accessor.Field); m.Key(k => k.Column("EntityId")); + m.Cascade(Mapping.ByCode.Cascade.All); }, r => r.OneToMany()); @@ -72,19 +73,12 @@ protected override void OnSetUp() { using var session = OpenSession(); using var transaction = session.BeginTransaction(); - - session.CreateSQLQuery( - "INSERT INTO Entity (Id) VALUES (0)" - ).ExecuteUpdate(); - session.CreateSQLQuery( - "INSERT INTO ChildEntity (Id, EntityId) VALUES (0, 0)" - ).ExecuteUpdate(); + var entity = new Entity { Id = EntityId.Id1 }; + entity.Children.Add(new ChildEntity { Id = 0 }); + entity.Children.Add(new ChildEntity { Id = 1 }); + session.Save(entity); - session.CreateSQLQuery( - "INSERT INTO ChildEntity (Id, EntityId) VALUES (1, 0)" - ).ExecuteUpdate(); - transaction.Commit(); } @@ -93,8 +87,8 @@ protected override void OnTearDown() using var session = OpenSession(); using var transaction = session.BeginTransaction(); - session.CreateSQLQuery("DELETE FROM ChildEntity").ExecuteUpdate(); - session.CreateSQLQuery("DELETE FROM Entity").ExecuteUpdate(); + session.CreateQuery("delete from ChildEntity").ExecuteUpdate(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); transaction.Commit(); } @@ -118,10 +112,10 @@ public async Task LoadsEntityWithEnumIdAndChildrenUsingQueryCacheAsync() using var session = OpenSession(); using var transaction = session.BeginTransaction(); var entity = (await (session - .Query() - .FetchMany(x => x.Children) - .WithOptions(opt => opt.SetCacheable(true)) - .ToListAsync(cancellationToken)))[0]; + .Query() + .FetchMany(x => x.Children) + .WithOptions(opt => opt.SetCacheable(true)) + .ToListAsync(cancellationToken)))[0]; await (transaction.CommitAsync(cancellationToken)); return entity;