考虑这个代表层次结构中节点的类:
public class Node
{
public Node()
{
Children = new List<Node>();
}
public virtual int Id { get; set; }
public virtual IList<Node> Children { get; set; }
public virtual Node Parent { get; set; }
public virtual int Position
{
get { return Parent == null ? -1 : Parent.Children.IndexOf(this); }
set { }
}
}
映射看起来像这样(因为NHibernate不支持双向关联中的列表,我在这里使用一个包并让孩子自动确定其位置):
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="AmbiguousColumn" assembly="AmbiguousColumn" xmlns="urn:nhibernate-mapping-2.2">
<class name="Node">
<id name="Id" type="Int32">
<generator class="identity" />
</id>
<bag name="Children" inverse="true" cascade="all" order-by="Position">
<key column="Parent" />
<one-to-many class="Node" />
</bag>
<many-to-one name="Parent" />
<property name="Position" />
</class>
</hibernate-mapping>
要使所有节点加载其子节点,我将使用如下查询:
var nodes = session.QueryOver<Node>()
.Fetch(x => x.Children).Eager
.List();
但是,执行此操作会导致异常:
NHibernate.Exceptions.GenericADOException:无法执行查询 [...(sql)...] ---&gt; System.Data.SqlClient.SqlException:不明确的列名称'Position'。
SQL:
SELECT
this_.Id as Id0_1_,
this_.Parent as Parent0_1_,
this_.Position as Position0_1_,
children2_.Parent as Parent3_,
children2_.Id as Id3_,
children2_.Id as Id0_0_,
children2_.Parent as Parent0_0_,
children2_.Position as Position0_0_
FROM
Node this_
left outer join
Node children2_
on this_.Id=children2_.Parent
ORDER BY
Position
我理解为什么会发生这种情况:NH连接同一个表两次,但使用order子句而不限定列名。
问题是:如何使此方案有效?因为我想要双向关系,所以可能难以取而代之。
在SO上有几个类似的问题,但我找不到实际的解决方案。
更新:错误是特定于数据库/驱动程序的。使用Sql Server CE(例如SqlServerCeDriver和MsSqlCe40Dialect),我得到了正确的查询。使用Sql Server(例如Sql2008ClientDriver和MsSql2012Dialect)会产生不合格的查询。
根据我自己的测试,这种行为仍然存在于github上的master分支中。
答案 0 :(得分:2)
我认为我找到了问题的原因和可行的解决方法:
问题的原因是该列被称为“位置”,根据http://msdn.microsoft.com/en-us/library/ms189822.aspx
,它是ODBC中的保留字。这与NH的hbm2ddl.keywords
属性的默认值设置为“关键字”这一事实相结合,导致NH无法限定order-by子句,可能是因为它虽然“Position”是关键字,但不是一栏。
http://nhforge.org/blogs/nhibernate/archive/2009/06/24/auto-quote-table-column-names.aspx
解决问题的方法:
1)为属性使用不同的名称 - 一个不是关键字的名称。在这种情况下,PositionInParent
可以毫无问题地工作。
2)使用反向标记正确引用order by子句。
<bag name="Children" inverse="true" cascade="all" order-by="`Position`">
或者您选择的映射API所需的任何内容,例如:按代码映射:
cls.Bag(x => x.Children,
map =>
{
map.Inverse(true);
map.Cascade(Cascade.All);
map.Key(key => key.Column("Parent"));
map.OrderBy("`Position`"); // note that you must not use a lambda expression in this case
},
map => map.OneToMany());
3)禁用关键字自动导入,即。将hbm2ddl.keywords
设置为none
(keywords
和auto-quote
都不起作用):
<property name="hbm2ddl.keywords">none</property>
或以编程方式:
config.DataBaseIntegration(db => db.KeywordsAutoImport = Hbm2DDLKeyWords.None);
您仍然可以在构建会话工厂之前调用SchemaMetadataUpdater.QuoteTableAndColumns
来自动引用保留字。
SchemaMetadataUpdater.QuoteTableAndColumns(config);
我现在坚持使用方法3,因为它是迄今为止最无痛的。