I'm using NHibernate 5.2.0.0 on .NET Framework 4.7.2 and I have noticed that it generates different SQL, depending on whether the LINQ/lambda expression is in a method or not. I would like to put it in a method (to make it reusable and more easy to refactor) and still get the better SQL generated.
Here is the whole statement, the relevant part is DocumentType = ...
. That's the part I want to use in like 100 other controllers too and don't want to copy & paste it there.
var billingDocumentList = from billingDoc in billingDocuments
where branchIdPermissions.Contains(billingDoc.Branch.Id)
let taxAmount = billingDoc.BillingDocumentPositions.Sum(p => p.Amount * (p.TaxRate / 100M))
select new BillingDocumentOverviewViewModel
{
Id = billingDoc.Id,
BillingDocumentNumber = billingDoc.BillingDocumentNumber,
DocumentStatus = billingDoc.DocumentStatus.Description,
BillingDocumentDate = billingDoc.BillingDocumentDate.Date,
Company = billingDoc.Branch.Company.CompanyNumber + "-" + billingDoc.Branch.Company.Description,
DocumentType =billingDoc.DocumentType.Description2.Translations.Where(x => x.Language.ISO2 == Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName || x.Language.ISO2 == "en").Select(n => new { n.Value, fallback = n.Language.ISO2 == "en" ? 1 : 0 }).OrderBy(x => x.fallback).Select(x => x.Value).FirstOrDefault(),
RecipientName1 = billingDoc.RecipientName1,
};
So I tried putting it into the Phrase
class (type of Description2
property)
public virtual string InCurrentLocale()
{
return Translations.Where(x => x.Language.ISO2 == Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName || x.Language.ISO2 == "en")
.Select(n => new { n.Value, fallback = n.Language.ISO2 == "en" ? 1 : 0 })
.OrderBy(x => x.fallback)
.Select(x => x.Value)
.FirstOrDefault();
}
While Nhibernates produces a (fast) subquery for the inline variant, it produces additional standalone queries when moving it into a method, what I want to avoid.
My goal is to produce the same SQL as in first variant, but make it reusable over a huge software product and keep the logic on one place.
I already tried playing around with the method implementation, with extension methods, but nothing worked.
Currently I'm trying to achieve what I want by digging deep into NHibernate. I'm trying to write a custom BaseHqlGeneratorForMethod
implementation and put all the Linq statements into that. That would be fine for me. Imaging something like that:
DocumentType = billingDocs.DocumentType.Description2.InCurrentLocale(),
.......
.....
public class InCurrentLocaleGenerator2 : BaseHqlGeneratorForMethod
{
public InCurrentLocaleGenerator2()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition<Phrase>((x) => x.InCurrentLocale())
};
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
Expression<Func<Translation, bool>> languageExp = (x) => x.Language.ISO2 == currentLanguage || x.Language.ISO2 == "en";
/// + the other parts of the complete LINQ statement
return visitor.Visit(languageExp).AsExpression();
..........
......
}
But I always get different errors, because I'm not well experiencced with writing Expression objects and all that stuff related to that.
Is there any way to achieve what I want? It has not be the HQL way, I would be very glad and thankful for every other idea/way or a guideline how to achieve that with HQL. Thanks!