nhibernate查询双链表

时间:2014-10-15 16:31:13

标签: c# nhibernate

CREATE TABLE [dbo].[Field](
    [FieldId] [int] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](500) NULL,
    [NextTaskTemplateFieldId] [int] NULL,
    [PreviousTaskTemplateFieldId] [int] NULL
) ON [PRIMARY]

insert into field values('chicken',2,3)
insert into field values('the',1,null)
insert into field values('home',null,2)
insert into field values('runs',3,1)

流利的nhibernate映射

public FieldMap()
        {
            Table("Field");
            Id(x => x.Id).Column("FieldId");
            Map(entity => entity.Name);
            Map(entity => entity.PreviousTaskTemplateFieldId);
            Map(entity => entity.NextTaskTemplateFieldId);
        }

您可以通过nhibernate查询帮助我,这将为我提供双链表的正确排序。

应按以下顺序返回:'the','Chicken''运行''home'

感谢 尼尔

1 个答案:

答案 0 :(得分:2)

在我开始之前,你的例子中有一个循环,这意味着任何迭代列表的尝试都会导致无限循环:

chicken --> the --> chicken --> the --> (etc)

本答案的其余部分假设您的链表结构中存在有效数据。


基本上有两种方法可以解决这个问题,我会详细介绍每种方法的优点和缺点。

首先,所有这些策略都涉及稍微更改您的Field类和您的映射:

public class Field
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    public virtual Field NextTaskTemplateField { get; set; }

    public virtual Field PreviousTaskTemplateField { get; set; }
}

映射

public class FieldMap : ClassMap<Field>
{
    public FieldMap()
    {
        Table("Field");
        Id(f => f.Id).Column("FieldId");
        Map(f => f.Name);
        References(f => f.NextTaskTemplateField)
            .Column("NextTaskTemplateFieldId");

        References(f => f.PreviousTaskTemplateField)
            .Column("PreviousTaskTemplateFieldId");
    }
}

这里的要点是我们已将引用映射到列表中的相邻字段,而不是仅保留列表中下一个和上一个节点的ID。

  1. 查询链表的“头部”,并简单地遍历相关节点。

    这很简单易懂,但它为每个节点生成一个select语句,这并不理想。基本上,只需抓住第一个节点(没有PreviousTaskTemplateField的节点)并循环,直到您所在的节点没有NextTaskTemplateField

    Field head = session.QueryOver<Field>()
        .Where(f => f.PreviousTaskTemplateField == null)
        .TransformUsing(Transformers.RootEntity)
        .SingleOrDefault();
    
    // If you know the FieldId of the Field you want to get as the front of the list,
    // just use session.Get<Field>(1);
    
    Field node = head;
    while (node != null)
    {
        Console.WriteLine(node.Name);
        node = node.NextTaskTemplateField;
    }
    

    同样,如果你关心性能,这不是一个理想的解决方案。每次加载列表中的下一个Field时,都会发出新的select语句。

  2. 使用递归CTE和命名查询

    由于看起来您正在使用SQL服务器,因此您可以使用递归CTE一次性获取整个列表。这是CTE的样子:

     
    with [Field_CTE]
    as
    (
        -- Base case: The node with no previous item
        select
            [Field].[FieldId],
            [Field].[Name],
            [Field].[NextTaskTemplateFieldId],
            [Field].[PreviousTaskTemplateFieldId],
            0 as [Index]
        from
            dbo.[Field]
        where
            [Field].[PreviousTaskTemplateFieldId] is null
    
        union all
    
        select
            [Field].[FieldId],
            [Field].[Name],
            [Field].[NextTaskTemplateFieldId],
            [Field].[PreviousTaskTemplateFieldId],
            [Field_CTE].[Index] + 1 as [Index]
        from
            dbo.[Field]
            inner join [Field_CTE] on 
                [Field_CTE].[NextTaskTemplateFieldId] = [Field].[FieldId]
    )
    select
        [Field_CTE].[FieldId],
        [Field_CTE].[Name],
        [Field_CTE].[NextTaskTemplateFieldId],
        [Field_CTE].[PreviousTaskTemplateFieldId]
    from
        [Field_CTE]
    order by
        [Field_CTE].[Index] asc;
    

    这将在一个查询中完整地为我们提供链接列表。您可能需要进行的一项更改是添加通过Id获取列表头的功能,而不是仅抓取Field而不使用之前的Field。此代码假定数据库中只有一个列表,但您应该可以对其进行扩展。

    不幸的是我们无法将其转换为NHibernate查询,因​​此我们必须创建一个命名查询。在执行此操作之前将CTE包装在存储过程中是最容易的,因此我创建了一个名为SP_GetFieldList的存储过程:

    create procedure SP_GetFieldList
    as
    begin
        -- CTE code above
    end
    

    然后,我们需要创建一个包含命名查询的* .hbm.xml文件:

    <?xml version="1.0" encoding="utf-8"?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <sql-query name="FieldListQuery">
        <!-- replace the "class" attribute with the location of your class -->
        <return alias="field" class="ConsoleApplication2.Field,ConsoleApplication2" />
        exec SP_GetFieldList
      </sql-query>
    </hibernate-mapping>
    

    最后,我们可以调用命名查询并映射到List<Field>

    IEnumerable<Field> linkedList = session.GetNamedQuery("FieldListQuery")
        .SetResultTransformer(Transformers.DistinctRootEntity)
        .List<Field>();
    

    这个策略的主要优点是我们只对整个列表执行一个SQL查询。