Search code examples
c#nhibernatepropertiesprojectionqueryover

NHibernate QueryOver fill property object in parent object with projections


I'm having a problem with my QueryOver and I don't understand why. My query returns the viewmodel object ContactInfo. I get this error on the Employee property: "could not find setter for property". How can I fill the Employee property within ContactInfo? What am I doing wrong?

ViewModel Object:

public class ContactInfo    
{
    public EmployeeInfo Employee { get; set; }
    public string Email { get; set; }
    public string InternalTelephone { get; set; }
}

Query

public override ContactInfo Execute()
    {
        ContactInfo r = null;
        EmployeeInfo ei = null;

        var result = Session.QueryOver<Job>()
            .JoinAlias(j => j.EmployeeInfo, () => ei)
            .Where(j => j.EmployeeInfo.Id == _employeeId)
            .Select(
                Projections.Property<Job>(j => ei.Id).WithAlias(() => r.Employee.Id),
                Projections.Property<Job>(j => ei.FirstName).WithAlias(() => r.Employee.FirstName),
                Projections.Property<Job>(j => ei.LastName).WithAlias(() => r.Employee.LastName),
                Projections.Property<Job>(j => ei.ReferenceCode).WithAlias(() => r.Employee.ReferenceCode),
                Projections.Property<Job>(j => j.Telefoon).WithAlias(() => r.InternalTelephone)
            )
            .TransformUsing(Transformers.AliasToBean<ContactInfo>())
            .Take(1)
            .SingleOrDefault<ContactInfo>();

        var email = Session.QueryOver<Employee>()
            .Where(e => e.Id == _employeeId)
            .Select(e => e.Email)
            .SingleOrDefault<string>();

        result.Email = email;

        return result;
    }
}

Solution

  • What we can do is to use different than default result transformer, e.g. DeepTransformer.

    In that case, the query must use Alias similar to the DTO model. So, if we have domain property ei.FirstName belonging to JoinAlias - j.EmployeeInfo - the alias must be reflecting the DTO ContactInfo - "EmployeeInfo.FirstName"

    .Select(        
        Projections.Property<Job>(j => ei.FirstName)         // the mapped domain model
                              .As("EmployeeInfo.FirstName"), // the path in DTO/view model
        ...
    )
    .TransformUsing(DeepTransformern<ContactInfo>()) // the DTO
    

    So now, the path "EmployeeInfo.FirstName" will be used to populate the Employee as a property EmployeeInfo and its property FirstName

    And this Result transformer

    DeepTransformer will use Alias to build up the reference tree. Could be used for references/many-to-one as well for IDictionary ... (but not for collections)

    NOTE: The .Is() method comes from some extensions, and can be replaced with != null like

    public static partial class Ext
    {
        public static bool Is(this object value)
        {
            return value != null;
        }
    ....