【问题标题】:Modify collation on NHibernate queries修改 NHibernate 查询的排序规则
【发布时间】:2013-09-12 20:08:01
【问题描述】:

我有一个大型 ASP.NET 应用程序,它使用 NHibernate (v3.2.0.4000) 来访问作为后端数据存储的 SQL Server 2005。我们有多个客户,每个客户都有自己的数据库,但所有数据库都运行在同一台服务器上。

快速问题:
如果不在数据库服务器上设置 COLLATION,或者单独的数据库本身,或者重写现有的单独 NHibernate 查询代码,我如何将自定义 COLLATION 语句添加到 NHibernate 查询中,纯粹是为了排序目的,在它们进入数据库之前?

更长的问题:
由于有一些新的国际客户,我们需要支持自定义排序规则,以便在我们的应用程序中进行排序。我们不能在数据库(或服务器)级别上执行此操作,因为我们在同一服务器上为每个客户托管一个单独的数据库(每个客户可能有不同的排序要求),并且存在诸如 TempDB 之类的复杂性和多个不同的同一服务器上的排序规则(请参阅 herehere)。

由于 NHibernate 在整个代码库中的使用方式存在差异(即 HQL、ICriteria、普通 SQL、QueryOver 和 Linq 的组合),并且由于其中许多查询非常复杂和混乱,我们希望完全避免更改任何现有的 NHibernate 查询代码。

SQL Server 将允许调用在该查询运行时应用特定排序规则的查询,例如:SELECT * FROM Customer ORDER BY Surname COLLATE Latin1_General_CI_AS

我还知道 NHibernate 允许创建一个 Interceptor,它可以在全局或特定于会话的基础上添加到 NHibernate(如 here 所示),允许在它之前拦截 SQL 语句正在发送到 SQL Server。

似乎我可以编写一个拦截器类来拦截 SQL 语句,但是,它只允许我捕获原始 SQL 语句(包括 NHibernate 的奇怪而美妙的字段和别名)。我不知道是否有任何方法可以干净地解析查询的特定部分(我只需要 ORDER BY 子句)并在检查字段后更改 ORDER BY 子句的各个组成部分(即各个字段)是基于文本的,并允许将 COLLATE 语句附加到它们。

NHibernate 公开了一个NHibernate.SqlCommand.ISqlStringVisitor 接口,这听起来很有希望,并且似乎可以在EmptyInterceptor 类的OnPrepareStatement 方法中捕获的SqlString 上运行,该方法可以被覆盖,但是,我对这部分完全不熟悉在我写这篇文章的时候,NHibernate 的权威信息源nhibernate.info 已经关闭(而且似乎已经有好几个星期了),这无济于事!

有没有人必须执行这样的任务?是否可以以干净且类型安全的方式解构 NHibernate 查询?是否有完全不同的方法可以实现相同的目标(考虑到无法更改数据库/服务器排序规则的相同限制)?

【问题讨论】:

  • 排序规则不仅涉及order by,而且实际上涉及任何字符串比较(相等,like,...)。尽管有可能(codetype.wordpress.com/2012/11/01/…)解析查询似乎是一项艰巨的工作,执行开销很大。也许为您的数据库考虑不同的排序规则是最简单的解决方案。
  • @jbl - 我知道排序不仅仅是排序,但是,为了我们的应用程序的目的,我们只需要关注排序。由于我现有帖子中提到的原因,在数据库级别使用不同的排序规则设置是不可行的。
  • 当您写“由于 NHibernate 在整个代码库中的使用方式不同...”时,您的意思是有 HQL、ICriteria、普通 SQL、QueryOver 和 Linq 吗? (我的代码库中有所有这些)我必须承认,除了解析查询或派生您自己的 NH 分支来覆盖所有涉及排序的位置之外,我没有看到其他选择
  • @jbl 是的,就是这样。我们在代码库中结合了所有这些方法。我有点认为解析我们生成的 SQL 查询可能是唯一的选择,但是,是否有可能以面向对象的方式执行此操作而不仅仅是字符串操作? (即,是否可以从原始 Sql 字符串中以编程方式对诸如 NHibernate“表达式树”之类的东西进行逆向工程?)
  • 我上面提供的链接确实提供了一种获取表达式树的方法,我认为拥有可怕的 150 美元库的公司是 sqlparser.com/sql-parser-dotnet.php 从未尝试过任何这些。

标签: sql nhibernate collation


【解决方案1】:

您可以使用自定义投影相对容易地做到这一点。下面的课程是一个很好的起点:

public class OrderByWithCollate : SimpleProjection
{
    public string ColumnName { get; private set; }
    public string Collation { get; private set; }

    public OrderByWithCollate(string columnName, string collation)
    {
        ColumnName = columnName;
        Collation = collation;
    }

    public override SqlString ToGroupSqlString(
        ICriteria criteria, 
        ICriteriaQuery criteriaQuery, 
        IDictionary<string, IFilter> enabledFilters)
    {
        throw new NotImplementedException();   
    }

    public override SqlString ToSqlString(
        ICriteria criteria, 
        int position, 
        ICriteriaQuery criteriaQuery, 
        IDictionary<string, IFilter> enabledFilters)
    {
        return new SqlStringBuilder()
            .Add(ColumnName)
            .Add(" COLLATE ").Add(Collation)
            .Add(" as __collate_").ToSqlString();
    }

    public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
    {
        return new IType[] {NHibernateUtil.String};
    }

    public override bool IsGrouped { get { return false; } }

    public override bool IsAggregate { get { return false; } }
}

但是,这种方式显然只适用于 CriteriaAPI 和 QueryOver:

session.QueryOver<Item>()
    .OrderBy(new OrderByWithCollate("Name", "Latin1_General_CI_AS")).Asc
    .List();

该查询的 SQL 类似于:

SELECT * FROM Item ORDER BY Name COLLATE Latin1_General_CI_AS asc

我不确定 HQL 或 LINQ,因为我很少使用它们,尤其是 LINQ,我从未使用过,从一开始就将其视为有毒资产,并明确劝阻我的队友不要使用它。

当然,这种方法需要更新现有查询,但我认为应用它不是很耗时,因为这是对标准 NHibernate 排序的替换,因此通过搜索和替换,您可以替换所有出现的排序。只需检查提供的条件和属性名称,您就可以安排一个复杂的 Collat​​ionProjection,它知道何时应用排序规则。

对于实现自定义投影,阅读 NHibernate 源代码是获取知识的好方法,因为几乎没有文档涵盖该主题。

我不认为拦截 IInterceptor.OnPrepareStatement 是一个好的选择,但是如果你想继续这个方向,那么你可以尝试从原始 SQL 字符串的部分重建 SQL 字符串,嗅探 ORDER BY 子句过程。我想没有人做过,所以你要做很多实验,会有很多奇怪的查询让你大吃一惊。

【讨论】:

  • 我希望避免更改(或重写)现有的 NHibernate 查询,因为这不仅是一项耗时的工作,而且很有可能在查询中引入细微的错误。我们的 NHibernate 查询非常广泛,并且(如另一条评论中所述)是 HQL、ICriteria、普通 SQL、QueryOver 和 Linq 的组合,因此应用 Projections 之类的东西可能会出现问题。似乎IInterceptor.OnPrepareStatement 是避免触及现有查询的一种可能方式,但当然,它也有其自身的显着复杂性。
  • 为了支持HQL,这个question有一个解决方案。一旦支持 HQL,对 Criteria 或 QueryOver 的支持应该是直截了当的,而支持 Linq 则可以像here 那样做更多的工作。但是所有这些也需要对所有查询进行代码更改。 IInterceptor 解决方案在我看来是唯一允许不触摸查询代码的解决方案,但正如您所说,这将非常棘手。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-10
  • 1970-01-01
  • 1970-01-01
  • 2013-12-30
  • 1970-01-01
  • 2014-03-02
相关资源
最近更新 更多