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'
感谢 尼尔
答案 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。
查询链表的“头部”,并简单地遍历相关节点。
这很简单易懂,但它为每个节点生成一个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
语句。
使用递归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查询。