2

I have an entity:

public class SalesUnit
{
    public virtual long Id { get; set; }
    public virtual string Name { get; set; }

}

And related Dto:

public class SalesUnitDto
{
    public long Id { get; set; }
    public string Name { get; set; }

}

I have a very simple query:

SalesUnitDto result = null; 
var list = _session.QueryOver<SalesUnit>()
                .SelectList(l => l
                    .Select(x => x.Id).WithAlias(() => result.Id)
                    .Select(x => x.Name).WithAlias(() => result.Name))
                .TransformUsing(Transformers.AliasToBean<SalesUnitDto>())
                //.Cacheable()
                .List<SalesUnitDto>();

It was working until I plugged in the second level cache. So if I uncomment Cacheable() line I will get the exception:

Message: Value cannot be null. Parameter name: aliases StackTrace:

   at NHibernate.Transform.AliasedTupleSubsetResultTransformer.IncludeInTransform(String[] aliases, Int32 tupleLength)
   at NHibernate.Transform.CacheableResultTransformer.Create(ITupleSubsetResultTransformer transformer, String[] aliases, Boolean[] includeInTuple)
   at NHibernate.Loader.Loader.GenerateQueryKey(ISessionImplementor session, QueryParameters queryParameters)
   at NHibernate.Loader.Loader.ListUsingQueryCache(ISessionImplementor session, QueryParameters queryParameters, ISet`1 querySpaces, IType[] resultTypes)
   at NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results)

So what's wrong with that? Is it a bug of NHibernate? I have tried different providers with no avail. Also I tried to create CacheableResultTransformer like this:

CacheableResultTransformer.Create(Transformers.AliasToBean<SalesUnitDto>(), new[] { "Id", "Name" }, new[] { true, true })

It can return and cache data but only as tuples(object[]). I did not manage to return Dto.

Here is the working example to demonstrate a problem: github

Roman Koliada
  • 4,286
  • 2
  • 30
  • 59
  • How is `result` declared? – Andrew Whitaker Dec 09 '16 at 15:48
  • @Andrew Whitaker, `result` is `SalesUnitDto` – Roman Koliada Dec 13 '16 at 14:08
  • Can't reproduce - works with or w/o `Cacheable()`. NH v4.0.4.4000 – Ivan Stoev Dec 14 '16 at 13:18
  • I'm also using NH v4.0.4.4000! Can it depends on Fluent configuration or CacheProvider? – Roman Koliada Dec 14 '16 at 13:32
  • Have no idea. If you can provide a full repro project or something (like OP in [Is it possible to fetch a link table without fetching all links?](http://stackoverflow.com/questions/40179771/is-it-possible-to-fetch-a-link-table-without-fetching-all-links/40448614#40448614) post), will take a look to see what's going on. – Ivan Stoev Dec 14 '16 at 17:39
  • @IvanStoev I've added a test project: https://github.com/koljada/NHibernateCacheTest. I would appreciate any help you can provide! – Roman Koliada Dec 15 '16 at 13:26
  • Thank you, I was able to reproduce it (in my previous test I didn't enable session caching). Unfortunately looks like this is an old (N)Hibernate bug/limitation - the caching changes the normal flow and does not supply aliases, which are essential for `AliasToBeanTransformer`. Looking for a workaround, but no success. See for instance http://nhusers.narkive.com/QUMSQjNn/using-setresulttransformer-transformers-aliastobean-and-setcacheable, or https://groups.google.com/forum/#!topic/nhusers/Ai8N1yi5UbQ – Ivan Stoev Dec 15 '16 at 17:37

2 Answers2

2

It turns out to be a bug/limitation of (N)Hibernate. When the caching is activated, the original IResultTransformer starts receiving null string[] aliases argument, which is essential for AliasToBeanTransformer implementation, hence the exception you are getting.

As a workaround I could suggest the following custom extension method and transformer, which stores the current aliases when called and passes them to the underlying AliasToBeanTransformer when the passed argument is null:

public static class NHExtensions
{
    public static IQueryOver<TRoot, TSubType> TransformUsingAliasToBean<TRoot, TSubType>(this IQueryOver<TRoot, TSubType> query, Type resultType)
    {
        ITupleSubsetResultTransformer resultTransformer = new AliasToBeanResultTransformer(resultType);
        var criteria = query.UnderlyingCriteria as NHibernate.Impl.CriteriaImpl;
        if (criteria != null)
            resultTransformer = new CacheableAliasToBeenResultTransformer(resultTransformer, criteria.Projection.Aliases);
        return query.TransformUsing(resultTransformer);
    }

    class CacheableAliasToBeenResultTransformer : ITupleSubsetResultTransformer
    {
        ITupleSubsetResultTransformer baseTransformer;
        string[] aliases;

        public CacheableAliasToBeenResultTransformer(ITupleSubsetResultTransformer baseTransformer, string[] aliases)
        {
            this.baseTransformer = baseTransformer;
            this.aliases = aliases;
        }

        public bool[] IncludeInTransform(string[] aliases, int tupleLength)
        {
            return baseTransformer.IncludeInTransform(aliases ?? this.aliases, tupleLength);
        }

        public bool IsTransformedValueATupleElement(string[] aliases, int tupleLength)
        {
            return baseTransformer.IsTransformedValueATupleElement(aliases ?? this.aliases, tupleLength);
        }

        public IList TransformList(IList collection)
        {
            return baseTransformer.TransformList(collection);
        }

        public object TransformTuple(object[] tuple, string[] aliases)
        {
            return baseTransformer.TransformTuple(tuple, aliases ?? this.aliases);
        }
    }
}

and you query would be:

SalesUnitDto result = null; 
var list = _session.QueryOver<SalesUnit>()
    .SelectList(l => l
        .Select(x => x.Id).WithAlias(() => result.Id)
        .Select(x => x.Name).WithAlias(() => result.Name))
    .TransformUsingAliasToBean(typeof(SalesUnitDto))
    .Cacheable()
    .List<SalesUnitDto>();

Tested and working for this scenario. Of course I can't guarantee that it works for all QueryOver variations.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
1

This bug of NHibernate is fixed in v4.1.0.4000!

Roman Koliada
  • 4,286
  • 2
  • 30
  • 59