Skip to content

Query cache breaks result transformers which need aliases #3713

@delta-emil

Description

@delta-emil

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions