NHibernate - 将 LINQ/Lambda 表达式移动到方法中会更改生成的 SQL - 为什么?

NHibernate - Moving LINQ/Lambda Expression into method changes the generated SQL - Why?

我在 .NET Framework 4.7.2 上使用 NHibernate 5.2.0.0,我注意到它会生成不同的 SQL,具体取决于 LINQ/lambda 表达式是否在方法中.我想把它放在一个方法中(使其可重用并且更容易重构)并且仍然得到更好的 SQL 生成。

这是完整的声明,相关部分是DocumentType = ...。这也是我想在其他 100 个控制器中使用的部分,不想将其复制并粘贴到那里。

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,
                                  };

所以我尝试将其放入 Phrase class(Description2 属性 的类型)

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();
}

虽然 Nhibernates 为内联变体生成了一个(快速)子查询,但它在将其移入方法时会生成额外的独立查询,这是我想避免的。

我的目标是生成与第一个变体相同的 SQL,但使其可在大型软件产品上重复使用,并将逻辑放在一个地方。

我已经尝试使用方法实现和扩展方法,但没有任何效果。

目前我正在努力通过深入研究 NHibernate 来实现我想要的。我正在尝试编写自定义 BaseHqlGeneratorForMethod 实现并将所有 Linq 语句放入其中。那对我来说很好。想象这样的事情:

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();
..........
......
}

但我总是会遇到不同的错误,因为我对编写 Expression 对象以及与之相关的所有东西没有很好的经验。

有什么方法可以达到我想要的效果吗?它不是 HQL 方式,我将非常高兴并感谢所有其他 idea/way 或如何使用 HQL 实现该方式的指南。谢谢!

这是因为 InCurrentLocale 不是表达式,没有任何 LINQ 提供程序可以解析它。 这里最简单的方法是使用 LINQKit 可以将表达式注入当前表达式树:

public virtual Expression<Func<IEnumerable<Translation>, string>> InCurrentLocale()
{
    return (translations) => 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();
}

并在您的查询中使用此功能。不要忘记放置 .AsExpandable()

var billingDocumentList = from billingDoc in billingDocuments.AsExpandable()
                          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 = InCurrentLocale().Invoke(billingDoc.DocumentType.Description2.Translations),
                                      RecipientName1 = billingDoc.RecipientName1,
                                  };