Summary
If you use query caching on a query with a result transformers which use the aliases array (e.g. AliasToBeanResultTransformer), when the data comes from cache, TransformTuple receives an empty aliases array, thus preventing the transformer from functioning correctly.
How to reproduce
This is a test you can add to src/NHibernate.Test/CacheTest/QueryCacheFixture.cs in order to reproduce the problem:
[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<SimpleDTO>())
.UniqueResult();
Assert.That(result1, Is.InstanceOf<SimpleDTO>());
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<SimpleDTO>())
.UniqueResult();
Assert.That(result2, Is.InstanceOf<SimpleDTO>());
var dto2 = (SimpleDTO)result2;
Assert.That(dto2.Id, Is.EqualTo(1)); // <-- the Id is 0 because the transformer failed
}
}
private class SimpleDTO
{
public long Id { get; set; }
public string Address { get; set; }
}
Investigation
What seems to happen is that:
A CustomLoader instance is used. Its GetResultList method relies on ReturnAliasesForTransformer/transformerAliases to get the alias array. transformerAliases could be filled either in the constructor (where in this case it is not filled because customQuery.CustomQueryReturns) and also in AutoDiscoverTypes.
In Loader.ListUsingQueryCache, when the data is not found the cache, DoList is called, which ends up calling AutoDiscoverTypes on the result transformer somewhere inside. However, when the data is found in the cache, nothing calls AutoDiscoverTypes on the result transformer and it remains the empty array created in the constructor.
As AutoDiscoverTypes uses the metadata returned by the database, and when using the cache, we are not doing a database query, the answer is probably to add the aliases to the cached data and read them out from there. And indeed they are stored in the cache and also read out by StandardQueryCache via GetResultsMetadata and then put into the QueryKey's CacheableResultTransformer by calling its SupplyAutoDiscoveredParameters method.
However those aliases never makes it back into the CustomLoader's transformerAliases, which is what the user-code-supplied transformer (in this case, the AliasToBeanResultTransformer) eventually gets.
Additional considerations
I encountered it with CreateSQLQuery/ISQLQuery, and I'm not sure if it happens with other types of queries.
Fix proposal
I created a fix proposal: #3714
I'm hoping to get it fixed in 5.5, because I'm having some trouble with 5.6's serialization change.
Summary
If you use query caching on a query with a result transformers which use the aliases array (e.g.
AliasToBeanResultTransformer), when the data comes from cache,TransformTuplereceives an emptyaliasesarray, thus preventing the transformer from functioning correctly.How to reproduce
This is a test you can add to
src/NHibernate.Test/CacheTest/QueryCacheFixture.csin order to reproduce the problem:Investigation
What seems to happen is that:
A
CustomLoaderinstance is used. ItsGetResultListmethod relies onReturnAliasesForTransformer/transformerAliasesto get the alias array.transformerAliasescould be filled either in the constructor (where in this case it is not filled becausecustomQuery.CustomQueryReturns) and also inAutoDiscoverTypes.In
Loader.ListUsingQueryCache, when the data is not found the cache,DoListis called, which ends up callingAutoDiscoverTypeson the result transformer somewhere inside. However, when the data is found in the cache, nothing callsAutoDiscoverTypeson the result transformer and it remains the empty array created in the constructor.As
AutoDiscoverTypesuses the metadata returned by the database, and when using the cache, we are not doing a database query, the answer is probably to add the aliases to the cached data and read them out from there. And indeed they are stored in the cache and also read out byStandardQueryCacheviaGetResultsMetadataand then put into theQueryKey'sCacheableResultTransformerby calling itsSupplyAutoDiscoveredParametersmethod.However those aliases never makes it back into the
CustomLoader'stransformerAliases, which is what the user-code-supplied transformer (in this case, theAliasToBeanResultTransformer) eventually gets.Additional considerations
I encountered it with
CreateSQLQuery/ISQLQuery, and I'm not sure if it happens with other types of queries.Fix proposal
I created a fix proposal: #3714
I'm hoping to get it fixed in 5.5, because I'm having some trouble with 5.6's serialization change.