-
Notifications
You must be signed in to change notification settings - Fork 930
Description
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.