From fa6170119f8901ae4dd380178ab2a9aeb1e7c026 Mon Sep 17 00:00:00 2001 From: delta-emil <9694906+delta-emil@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:07:48 +0200 Subject: [PATCH] fix #3713 - query cache result transformer aliases bug --- .../Async/CacheTest/QueryCacheFixture.cs | 36 +++++++++++++++++++ .../CacheTest/QueryCacheFixture.cs | 36 +++++++++++++++++++ src/NHibernate/Async/Loader/Loader.cs | 1 + src/NHibernate/Loader/Custom/CustomLoader.cs | 8 +++++ src/NHibernate/Loader/Loader.cs | 5 +++ 5 files changed, 86 insertions(+) diff --git a/src/NHibernate.Test/Async/CacheTest/QueryCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/QueryCacheFixture.cs index 6209bb44853..6e9860e9884 100644 --- a/src/NHibernate.Test/Async/CacheTest/QueryCacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/QueryCacheFixture.cs @@ -11,6 +11,7 @@ using System.Collections; using NHibernate.Cfg; using NHibernate.DomainModel; +using NHibernate.Transform; using NUnit.Framework; using Environment = NHibernate.Cfg.Environment; @@ -101,5 +102,40 @@ public async Task QueryCacheWithScalarReturnAsync() Assert.That(result, Is.EqualTo(200012), "Unexpected cached result"); } } + + [Test] + public async Task QueryCacheWithAliasToBeanTransformerAsync() + { + using (var s = OpenSession()) + { + const string query = "select s.id_ as Id from Simple as s"; + var result1 = await (s + .CreateSQLQuery(query) + .SetCacheable(true) + .SetResultTransformer(Transformers.AliasToBean()) + .UniqueResultAsync()); + + Assert.That(result1, Is.InstanceOf()); + var dto1 = (SimpleDTO)result1; + Assert.That(dto1.Id, Is.EqualTo(1)); + + // Run a second time, just to test the query cache + var result2 = await (s + .CreateSQLQuery(query) + .SetCacheable(true) + .SetResultTransformer(Transformers.AliasToBean()) + .UniqueResultAsync()); + + Assert.That(result2, Is.InstanceOf()); + var dto2 = (SimpleDTO)result2; + Assert.That(dto2.Id, Is.EqualTo(1)); + } + } + + private class SimpleDTO + { + public long Id { get; set; } + public string Address { get; set; } + } } } diff --git a/src/NHibernate.Test/CacheTest/QueryCacheFixture.cs b/src/NHibernate.Test/CacheTest/QueryCacheFixture.cs index ddd4658ea89..d18ad430f52 100644 --- a/src/NHibernate.Test/CacheTest/QueryCacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/QueryCacheFixture.cs @@ -1,6 +1,7 @@ using System.Collections; using NHibernate.Cfg; using NHibernate.DomainModel; +using NHibernate.Transform; using NUnit.Framework; using Environment = NHibernate.Cfg.Environment; @@ -90,5 +91,40 @@ public void QueryCacheWithScalarReturn() Assert.That(result, Is.EqualTo(200012), "Unexpected cached result"); } } + + [Test] + public void QueryCacheWithAliasToBeanTransformer() + { + using (var s = OpenSession()) + { + const string query = "select s.id_ as Id from Simple as s"; + var result1 = s + .CreateSQLQuery(query) + .SetCacheable(true) + .SetResultTransformer(Transformers.AliasToBean()) + .UniqueResult(); + + Assert.That(result1, Is.InstanceOf()); + var dto1 = (SimpleDTO)result1; + Assert.That(dto1.Id, Is.EqualTo(1)); + + // Run a second time, just to test the query cache + var result2 = s + .CreateSQLQuery(query) + .SetCacheable(true) + .SetResultTransformer(Transformers.AliasToBean()) + .UniqueResult(); + + Assert.That(result2, Is.InstanceOf()); + var dto2 = (SimpleDTO)result2; + Assert.That(dto2.Id, Is.EqualTo(1)); + } + } + + private class SimpleDTO + { + public long Id { get; set; } + public string Address { get; set; } + } } } diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index f797434cbe4..1fbcac0933d 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -1391,6 +1391,7 @@ private async Task ListUsingQueryCacheAsync(ISessionImplementor session, else { result = queryCacheBuilder.GetResultList(result); + ApplyCachedMetadata(key.ResultTransformer); } result = TransformCacheableResults(queryParameters, key.ResultTransformer, result); diff --git a/src/NHibernate/Loader/Custom/CustomLoader.cs b/src/NHibernate/Loader/Custom/CustomLoader.cs index fb5606a283d..3283bd1cef9 100644 --- a/src/NHibernate/Loader/Custom/CustomLoader.cs +++ b/src/NHibernate/Loader/Custom/CustomLoader.cs @@ -380,6 +380,14 @@ public override void AutoDiscoverTypes( queryParameters.ResultTransformer, transformerAliases); } + protected override void ApplyCachedMetadata(CacheableResultTransformer resultTransformer) + { + if (transformerAliases.Length == 0 && resultTransformer?.AutoDiscoveredAliases?.Length > 0) + { + transformerAliases = resultTransformer.AutoDiscoveredAliases; + } + } + protected override void ResetEffectiveExpectedType(IEnumerable parameterSpecs, QueryParameters queryParameters) { parameterSpecs.ResetEffectiveExpectedType(queryParameters); diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index ad3b5109021..01a036fd4da 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1894,6 +1894,7 @@ private IList ListUsingQueryCache(ISessionImplementor session, QueryParameters q else { result = queryCacheBuilder.GetResultList(result); + ApplyCachedMetadata(key.ResultTransformer); } result = TransformCacheableResults(queryParameters, key.ResultTransformer, result); @@ -1901,6 +1902,10 @@ private IList ListUsingQueryCache(ISessionImplementor session, QueryParameters q return GetResultList(result, queryParameters.ResultTransformer); } + protected virtual void ApplyCachedMetadata(CacheableResultTransformer resultTransformer) + { + } + public IList TransformCacheableResults(QueryParameters queryParameters, CacheableResultTransformer transformer, IList result) { var resolvedTransformer = ResolveResultTransformer(queryParameters.ResultTransformer);