Nhibernate Aliastobean Transformer Associations

NHibernate AliasToBean transformer throws then the QueryOver alias is a private field

So after some hard search, I found the answer.
NHibernate had some conflicts with "Roslyn" compiler, but they fixed them in next versions 4.0.4, 3.4.1, and 3.3.5.
Just updating NHibernate to one of this version, fixed my problem.

Use NHibernate AliasToBean Transformer launch n+1 query

In general there are two ways.

1) we have to use Projections, and project all properties to be selected explicitly. Then we have to use some deeper transformation e.g. Deep transformer here

2) we can use batch fetching, which will load all related relations in few batches. That will lead to 1 + 2(4) queries, to load all relations in separated (few) queries

For more details about projections:

  • How to partially project a child object with many fields in nHibernate

For more details about batch fetching you can check this:

  • documentation - http://nhibernate.info/doc/nh/en/index.html#performance-fetching-batch
  • How to Eager Load Associations without duplication in NHibernate? or
  • NHibernate: Select one to Many Left Join - Take X latest from Parent

nHibernate joining multiple tables and using AliasToBean Transformer

If you use Queryover then this is the only way.

If you really think less lines of code are preferable, more intuitive, or sits better with you then you can do either:-

  1. Create a DB view and create mapings files for the view with
    mutable="false" in your class definition and use protected set; on your class properties
  2. Use the LINQ provider instead e.g. .Query (see
    this blog post for more info)

How to partially project a child object with many fields in nHibernate

We can indeed use custom transformer. There is one, which I am using for a really very very deep projections (inlcuding dynamic objects - 5.1.13. component, dynamic-component)

  • DeepTransformer<TEntity>

Take it (if needed adjust it) and your final query could be like this

// just the last lines are different
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)

// the native WitAlias would not work, it uses expression
// to extract just the last property
//.Select(c => c.Teacher.ID).WithAlias(() => courseAlias.Teacher.ID)
//.Select(c => c.Teacher.Name).WithAlias(() => courseAlias.Teacher.Name))

// so we can use this way to pass the deep alias
.Select(Projections.Property(() => teacherAlias.ID).As("Teacher.ID"))
.Select(Projections.Property(() => teacherAlias.Name).As("Teacher.Name"))

// instead of this
// .TransformUsing(Transformers.AliasToBean<CourseItem>())
// use this
.TransformUsing(new DeepTransformer<CourseItem>())

And in case, that your aliases do match to property names, that transformer will built the object tree...

Nhibernate Group By and Alias To Bean

In general, if we want to change our projection to be using GROUP BY, we have to change all "SELECT" parts to be either part of GROUP BY or SUM, MIN ...

We can do it with this kind of syntax

// firstly 
// the original part from the question above
baseQuery.SelectList(list => list
...
.Select(() => joinedBriefBestemmeling.BinnenLand)
.WithAlias(() => nieuwePrintopdrachtInfo.BestemmelingBinnenLand)
.Select(() => joinedVerbruiksAdres.Land)
.WithAlias(() => nieuwePrintopdrachtInfo.LandVerbuiksadres)
.Select(Projections.Property(() => joinedContactpersoon.ContactpersoonId)
.As("BestemmelingContactPersoon.ContactPersoonId"))
.Select(Projections.Property(() => joinedContactpersoonType.Omschrijving)
.As("BestemmelingContactPersoon.TypeContactPersoon"))
...

// changed, to use GROUP BY
baseQuery.SelectList(list => list
...
.SelectGroup(() => joinedBriefBestemmeling.BinnenLand)
.WithAlias(() => nieuwePrintopdrachtInfo.BestemmelingBinnenLand)
.SelectGroup(() => joinedVerbruiksAdres.Land)
.WithAlias(() => nieuwePrintopdrachtInfo.LandVerbuiksadres)
.Select
(Projections.Alias
(Projections.GroupProperty
(Projections.Property(() => joinedContactpersoon.ContactpersoonId))
, "BestemmelingContactPersoon.ContactPersoonId"))
.Select
(Projections.Alias
(Projections.GroupProperty
(Projections.Property(() => joinedContactpersoonType.Omschrijving))
, "BestemmelingContactPersoon.TypeContactPersoon"))
...

So, now we have the GROUP BY (instead of just a SELECT) replacing the original code. But we can do more, we can introduce these (just a quick version) Extension methods (just a light version, really - but working)

public static class Extensions
{
public static NHibernate.Criterion.Lambda.QueryOverProjectionBuilder<T> GroupByProperty<T>(
this NHibernate.Criterion.Lambda.QueryOverProjectionBuilder<T> builder,
System.Linq.Expressions.Expression<Func<object>> propertyExpression,
System.Linq.Expressions.Expression<Func<object>> aliasExpression)
{
var alias = aliasExpression.ParseProperty();

var propertyProjection = Projections.Property(propertyExpression);
var groupProjection = Projections.GroupProperty(propertyProjection);
var withAliasProjection = Projections.Alias(groupProjection, alias);

builder.Select(withAliasProjection);
return builder;
}

public static string ParseProperty<TFunc>(this System.Linq.Expressions.Expression<TFunc> expression)
{
var body = expression.Body as System.Linq.Expressions.MemberExpression;
if (body.IsNull())
{
return null;
}

string propertyName = body.Member.Name;

ParseParentProperty(body.Expression as System.Linq.Expressions.MemberExpression, ref propertyName);

// change the alias.ReferenceName.PropertyName
// to just ReferenceName.PropertyName
var justAPropertyChain = propertyName.Substring(propertyName.IndexOf('.') + 1);
return justAPropertyChain;
}

static void ParseParentProperty(System.Linq.Expressions.MemberExpression expression, ref string propertyName)
{
if (expression.IsNull())
{
return;
}
// Parent.PropertyName
propertyName = expression.Member.Name + "." + propertyName;

ParseParentProperty(expression.Expression as System.Linq.Expressions.MemberExpression, ref propertyName);
}
}

And the above code could be made more readable and common, without any magic string

baseQuery.SelectList(list => list
...
.GroupByProperty(() => joinedBriefBestemmeling.BinnenLand)
,() => nieuwePrintopdrachtInfo.BestemmelingBinnenLand)
.GroupByProperty(() => joinedVerbruiksAdres.Land)
,() => nieuwePrintopdrachtInfo.LandVerbuiksadres)
.GroupByProperty(() => joinedContactpersoon.ContactpersoonId)
.() => nieuwePrintopdrachtInfo.BestemmelingContactPersoon.ContactPersoonId)
.GroupByProperty(() => joinedContactpersoonType.Omschrijving)
.() => nieuwePrintopdrachtInfo.BestemmelingContactPersoon.TypeContactPersoon)
...

NOTE IsNull() is also extension

Dynamic DTO from nhibernate projections criteria

What we need is transformer:

criteria
.SetResultTransformer(
NHibernate.Transform.Transformers.AliasToBean<MyEntity>())

or without generic

criteria
.SetResultTransformer(
NHibernate.Transform.Transformers.AliasToBean(this.modelType))

The point with transformers is to use aliasing (see the .As()):

.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Property1").As("Property1"))
.Add(Projections.Property("Property2").As("Property2"))
.Add(Projections.Property("Property3").As("Property3"))
)

Check more here e.g.:

  • How to partially project a child object with many fields in nHibernate
  • NHibernate AliasToBean transformer associations

Fluent NHibernate - ProjectionList - ICriteria is returning null values

You are almost there. What we need, is to properly convert Projections into entity/object tree. That would require two steps:

I. use the alias for each column

Column Alias, is useful more for ex post processing, than for SQL statement generation. But it is a must for next step. So instead of this:

columns.Add(Projections.Property("Manager.Id"));
columns.Add(Projections.Property("Manager.Name"));
columns.Add(Projections.Property("Manager.Document"));

we need this:

columns.Add(Projections.Property("Manager.Id").As("Manager.Id");
columns.Add(Projections.Property("Manager.Name").As("Manager.Name"));
columns.Add(Projections.Property("Manager.Document").As("Manager.Document"));

In fact, this would be enough, if we are using the first level (no JOIN) entity. For JOINed reference tree (many-to-one) it won't work. but

II. use custom result transformer

As always, NHibernate provides many open points for custom extensions. One of them would be the Custom IResultTransformer. The one, ready to handle reference tree we need is here:

  • DeepTransformer

Having that in our solution we should instead of this:

searchCriteria.SetResultTransformer(Transformers.AliasToBean<T>());

use this:

searchCriteria.SetResultTransformer(new DeepTransformer<T>());

This implemenation is strongly dependent on proper alias setting, describing the real entity properties (to use reflection to find what to set). So the first point - column/property alias is really essential



Related Topics



Leave a reply



Submit