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:-
- Create a DB view and create mapings files for the view with
mutable="false"
in your class definition and useprotected set;
on your class properties - 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
Parsing Ftpwebrequest Listdirectorydetails Line
C# Image Resizing to Different Size While Preserving Aspect Ratio
Reliably Stop System.Threading.Timer
Newtonsoft.JSON Cannot Convert Model with Typeconverter Attribute
Can You Get the Column Names from a SQLdatareader
C#:What If a Static Method Is Called from Multiple Threads
Is Accessing a Variable in C# an Atomic Operation
Wpf: How to Dynamically Add Controls in Dynamically Created Wpf Window
Prevent Using Dispatcher.Invoke in Wpf Code
Nhibernate Aliastobean Transformer Associations
Entitytype Has No Key Defined Error
ASP.NET Core MVC:How to Get Raw JSON Bound to a String Without a Type
How Does JSON Deserialization in C# Work
Restsharp JSON Parameter Posting