5

I'm trying to use the following statement to get an entity with the fields I'm after:

retVal = session.CreateCriteria(typeof(MyEntity))
            .CreateAlias("MyEntityProperty", "MyEntityProperty")
            .Add(Restrictions.Eq("MyEntityProperty.Year", year))
            .SetProjection(
                Projections.Distinct(
                    Projections.ProjectionList()
                        .Add(Projections.Property("Property1"), "Property1")
                        .Add(Projections.Property("Property2"), "Property2")
                        .Add(Projections.Property("MyEntityProperty.RegisteredUser"), "MyEntityProperty.RegisteredUser")
                        .Add(Projections.Property("MyEntityProperty.CompanyInfo"), "MyEntityProperty.CompanyInfo")
                                                )
            )
            .SetResultTransformer(Transformers.AliasToBean(typeof(MyEntity)))
            .List<MyEntity>()
            .Cast<BaseMyEntity>();

MyEntity is the entity I want to return, and MyEntityProperty is a property of MyEntity that is another entity (of type MyEntityProperty).

The error I get is Could not find a setter for property 'MyEntityProperty.RegisteredUser' in class 'MyEntity'

Is the AliasToBean transformer not able to handle sub entities? Or is there something more I need to do to make it work?

tomunderhill
  • 301
  • 1
  • 4
  • 11

2 Answers2

22

There is my master piece... which I'm using to transform any level of projections depth. Take it and use it like this:

.SetResultTransformer(new DeepTransformer<MyEntity>())

It could be used for any ValueType properties, many-to-one references and also for dynamic objects...

public class DeepTransformer<TEntity> : IResultTransformer
    where TEntity : class
{
    // rows iterator
    public object TransformTuple(object[] tuple, string[] aliases)
    {
        var list = new List<string>(aliases);

        var propertyAliases = new List<string>(list);
        var complexAliases = new List<string>();

        for(var i = 0; i < list.Count; i++)
        {
            var aliase = list[i];
            // Aliase with the '.' represents complex IPersistentEntity chain
            if (aliase.Contains('.'))
            {
                complexAliases.Add(aliase);
                propertyAliases[i] = null;
            }
        }

        // be smart use what is already available
        // the standard properties string, valueTypes
        var result = Transformers
             .AliasToBean<TEntity>()
             .TransformTuple(tuple, propertyAliases.ToArray());

        TransformPersistentChain(tuple, complexAliases, result, list);

        return result;
    }

    /// <summary>Iterates the Path Client.Address.City.Code </summary>
    protected virtual void TransformPersistentChain(object[] tuple
          , List<string> complexAliases, object result, List<string> list)
    {
        var entity = result as TEntity;

        foreach (var aliase in complexAliases)
        {
            // the value in a tuple by index of current Aliase
            var index = list.IndexOf(aliase);
            var value = tuple[index];
            if (value.IsNull())
            {
                continue;
            }

            // split the Path into separated parts
            var parts = aliase.Split('.');
            var name = parts[0];

            var propertyInfo = entity.GetType()
                  .GetProperty(name, BindingFlags.NonPublic 
                                   | BindingFlags.Instance 
                                   | BindingFlags.Public);

            object currentObject = entity;

            var current = 1;
            while (current < parts.Length)
            {
                name = parts[current];
                object instance = propertyInfo.GetValue(currentObject);
                if (instance.IsNull())
                {
                    instance = Activator.CreateInstance(propertyInfo.PropertyType);
                    propertyInfo.SetValue(currentObject, instance);
                }

                propertyInfo = propertyInfo.PropertyType.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
                currentObject = instance;
                current++;
            }

            // even dynamic objects could be injected this way
            var dictionary = currentObject as IDictionary;
            if (dictionary.Is())
            {
                dictionary[name] = value;
            }
            else
            {
                propertyInfo.SetValue(currentObject, value);
            }
        }
    }

    // convert to DISTINCT list with populated Fields
    public System.Collections.IList TransformList(System.Collections.IList collection)
    {
        var results = Transformers.AliasToBean<TEntity>().TransformList(collection);
        return results;
    }
}
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • I've actually worked out I was making things more complicated than I needed to, but thanks a lot for your code, it looks really useful. – tomunderhill Jun 17 '14 at 08:44
  • dictionary.Is() Is this like a "to be or not to be" kind of thing? Or a null check of some sort? – user99999991 Mar 04 '16 at 22:44
  • 1
    @user999999928 correct.. that is just an extension, checking null... *(providing it through any kind of types, including ValueTypes - abstraction with fluent syntax)* – Radim Köhler Mar 05 '16 at 05:12
  • @RadimKöhler. Where do extensions come from: `.IsNull()` and `.Is()`? And what do they do? – Fernando Leal Jan 16 '18 at 11:07
  • @FernandoLeal ... kind of ["write into language"](https://www.safaribooksonline.com/library/view/code-complete-second/0735619670/ch34s04.html) principle *(do read **Code complete**)*.. easily kind of `public static bool Is(this object value) {return value != null;}` with advantage - it could be reused in other languages *(e.g. Typescript etc...)* – Radim Köhler Jan 16 '18 at 11:11
1

Well wasn't I making things more complicated than necessary.

Instead of trying to set the fields on the sub entity, all I needed to do was reference the entity field itself:

.Add(Projections.Property("MyEntityProperty"), "MyEntityProperty")

and nHibernate populated it fine.

But I'm glad I asked because I got Radim's very useful code :-)

tomunderhill
  • 301
  • 1
  • 4
  • 11