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,
};
我在 .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,
};