NHibernate 中的自然排序

Natural Sorting in NHibernate

我正在尝试使用 NHibernate 在我的 C# 代码中进行自然排序。

我正在使用以下代码在 NHibernate 中进行排序。 "Code" 是 "Item" table 的 nvarchar 字段。

public IQueryable<Item> SearchItems(string code)
        {
            var session = GetSession();
            var criteria = session.CreateCriteria(typeof(Item)).Add(Restrictions.InsensitiveLike("Code", code + "%"));
            var items = criteria.AddOrder(Order.Asc("Code")).List<Item>().AsQueryable();
            return items;
        }

使用上面的代码我得到类似

的输出
item1, 
item10, 
item100, 
item2, 
item20, ...

但我想要

item1, 
item2, 
..., 
item10, 
..., 
item20, 
..., 
item100...

如何将其替换为自然排序?

A) 你可以在 SQL 中完成它,但它变得相当复杂(因为从 NHibernate 引用 SQL 函数非常复杂,and/or 它相当 complex/nearly 无法使用来自 NHiberante 的 ROW_NUMBER())

B) 您可以在 SQL 中的两个单独的附加列中对数据进行反规范化。当你写入数据时,做起来更复杂,但是排序是 "free":你必须创建一个 CodePrefix (varchar) 列和一个 CodeNumber (numeric nullable) 列,并在其中保存拆分后的值。

C) 您已经在您的代码片段中具体化查询(您的查询在 List<Item>() 中执行),并且您没有使用分页,因此您可以对 C# 端的项目进行排序("client side").为此,您可能可以查看 Natural Sort Order in C# . There are various solutions there, one of which is http://zootfroot.blogspot.it/2009/09/natural-sort-compare-with-linq-orderby.html

我想向您展示我们如何在这些场景中使用 NHibernate。我将使用 SQL 服务器语法,但背后的想法适用于任何方言。

SQL 服务器 2012

首先,让我们同意,这个 SQL 语句将给我们 NUMBER 部分:

ISNULL(TRY_PARSE(SUBSTRING(Code, (PATINDEX('%[0-9]%', Code)), LEN(Code)) as int), 0)
   AS [NUM]

它确实找到第一个数字 0-9 的字符,并将其作为子字符串并调用 try_parse 以获取数字(如果 ISNULL 则为 0)

这可以提取文本部分,并处理缺少数字的情况(例如值 'codeX' 而不是 'code0')

LEFT(Code, IIF( PATINDEX('%[0-9]%', Code) > 0, PATINDEX('%[0-9]%', Code) - 1, 0))
   AS [TXT]

现在我们可以将所有这些放在一起并与投影一起使用此查询,它正在执行 自然排序

// text part as projection
var textPart = Projections.SqlProjection(
    "LEFT(Code, IIF( PATINDEX('%[0-9]%', Code) > 0, PATINDEX('%[0-9]%', Code) - 1, 0)) AS [TXT]"
    , new string[] {}
    , new IType[] {}
    );

// number part as projection
var numberPart = Projections.SqlProjection(
    " ISNULL(TRY_PARSE(SUBSTRING(Code, (PATINDEX('%[0-9]%', Code)), LEN(Code)) as int), 0) AS [NUM]"
    , new string[] {}
    , new IType[] {}
    );
        ;

var criteria = session
      .CreateCriteria<Item>()
      .Add(Restrictions.InsensitiveLike("Code", code + "%"));

var items = criteria
      // here we order by our projectons
      .AddOrder(Order.Asc(textPart))
      .AddOrder(Order.Asc(numberPart))
      .List<Item>();

延长:

应该有working life example

我创建了一个显示上述解决方案的脚本。此脚本可以 运行 针对任何 MS SQL 服务器 2012 DB:

IF OBJECT_ID('[dbo].[MyTable]', 'U') IS NOT NULL
    DROP TABLE [dbo].[MyTable]

CREATE TABLE [dbo].[MyTable] (
    [My_ID] [int] IDENTITY(1,1) NOT NULL,
    [Code] [varchar](255) NOT NULL,
    CONSTRAINT [PK_My_ID] PRIMARY KEY CLUSTERED 
  (
    [My_ID] ASC
  )
)

INSERT INTO [dbo].[MyTable] ([Code])
VALUES ('Code1')
, ('Code2')
, ('Code10')
, ('Code20')
, ('Code100')
, ('Code200')

-- SELECT [Code] FROM [dbo].[MyTable] ORDER BY [Code]

SELECT [Code] FROM [dbo].[MyTable] ORDER BY
 LEFT(Code, IIF( PATINDEX('%[0-9]%', Code) > 0, PATINDEX('%[0-9]%', Code) - 1, 0))
 ,
 ISNULL(TRY_PARSE(SUBSTRING(Code, (PATINDEX('%[0-9]%', Code)), LEN(Code)) as int), 0)

试试看here

对于其他数据库引擎(或之前的版本),只需调整 SELECT 脚本,但应该清楚如何与 NHibernate 一起使用它

SQL 服务器 2008

有一个版本的小草稿可以用于 sql server 2008

SELECT [Code] FROM [dbo].[MyTable] ORDER BY
 LEFT(Code, (CASE WHEN PATINDEX('%[0-9]%', Code) > 0 THEN PATINDEX('%[0-9]%', Code) - 1 ELSE 0 END) )
 ,
 ISNULL(CONVERT(decimal(18,3), (SUBSTRING(Code, (PATINDEX('%[0-9]%', Code)), LEN(Code)))), 0)