我正在使用NHibernate和Fluent映射,并且当我加入多对多关系时遇到重复条目的问题。我下面的简单示例有两个类,PurchaseOrder和Product。 PurchaseOrder可以有许多产品,而产品可以是许多PurchaseOrders的一部分。
当我尝试检索PurchaseOrder及其产品时,我会为每个产品重复相同的PurchaseOrder。 (因此,如果PurchaseOrder有5个产品,我将在我的结果中看到相同的PurchaseOrder 5次。每个产品都有5个产品。)
这是我的设置:
PurchaseOrder
OrderID OrderDate
1 2013-01-01
2 2013-01-02
Product
ProductID Name
1 Widget
2 Thing
OrderProducts
OrderID ProductID
1 1
1 2
2 1
2 2
public class PurchaseOrder
{
public virtual int OrderID { get; set; }
public virtual DateTime? OrderDate { get; set; }
public virtual IList<Product> Products { get; set; }
}
public class Product
{
public virtual int ProductID { get; set; }
public virtual string Name { get; set; }
public virtual IList<PurchaseOrder> Orders { get; set; }
}
public class PurchaseOrderMapping : ClassMap<PurchaseOrder>
{
public PurchaseOrderMapping()
{
Id(x => x.OrderID, "OrderID");
Map(x => x.OrderDate, "OrderDate");
HasManyToMany(x => x.Products)
.Table("OrderProducts")
.Schema("dbo")
.ParentKeyColumn("OrderID")
.ChildKeyColumn("ProductID");
Schema("dbo");
Table("PurchaseOrder");
}
}
public class ProductMapping : ClassMap<Product>
{
public ProductMapping ()
{
Id(x => x.ProductID, "ProductID");
Map(x => x.Name, "Name");
HasManyToMany(x => x.Orders)
.Table("OrderProducts")
.Schema("dbo")
.ParentKeyColumn("OrderID")
.ChildKeyColumn("ProductID")
.Inverse();
Schema("dbo");
Table("Product");
}
}
var orderList = session.QueryOver<PurchaseOrder>()
.JoinQueryOver<Product>(o => o.Products)
.List();
我希望orderList有2个PurchaseOrders,但实际上有4个。重复对应于OrderID = 1的对象,OrderID = 2
foreach(var o in orderList) { Console.WriteLine(o.OrderID); }
Output:
1
1
2
2
进一步说,如果我比较具有相同ID的对象,它们就是同一个对象。
System.Console.WriteLine(Object.ReferenceEquals(orderList[0], orderList[1]));
System.Console.WriteLine(Object.ReferenceEquals(orderList[2], orderList[3]));
Output:
True
True
为什么NHibernate会复制结果中的对象?如何排除它们并获得我的2个订单列表,每个订单包含相应的2个产品?
答案 0 :(得分:5)
出于某种原因,session.QueryOver<T>
不会立即返回明显的结果,您必须通过结果转换器或Linq .Distinct()
var orderList = session.QueryOver<PurchaseOrder>()
.Fetch(p => p.Products).Eager
.List()
.Distinct();
或
var orderListFetch = session.QueryOver<PurchaseOrder>()
.Fetch(p => p.Products).Eager
.TransformUsing(Transformers.DistinctRootEntity)
.List();
或者你也可以使用Nhibernate.Linq:session.Query<T>
接口,这个接口确实会默认返回一个不同的结果:
var linqQuery = session.Query<PurchaseOrder>()
.Fetch(p => p.Products).ToList();
所有3个查询都会生成几乎完全相同的SQL语句,这些语句都会返回4行,因为它使用左外连接 ...
结果将始终转换为内存中的不同结果集!
测试设置
我稍微更改了您的代码,切换了父键和子键。 对于插入/更新/删除子记录,您可能还希望保留级联
public class PurchaseOrder
{
public virtual int OrderID { get; set; }
public virtual DateTime? OrderDate { get; set; }
public virtual IList<Product> Products { get; set; }
}
public class Product
{
public virtual int ProductID { get; set; }
public virtual string Name { get; set; }
public virtual IList<PurchaseOrder> Orders { get; set; }
}
public class PurchaseOrderMapping : ClassMap<PurchaseOrder>
{
public PurchaseOrderMapping()
{
Id(x => x.OrderID, "OrderID");
Map(x => x.OrderDate, "OrderDate");
HasManyToMany(x => x.Products)
.Table("OrderProducts")
.Schema("dbo")
.ParentKeyColumn("ProductID")
.ChildKeyColumn("OrderID")
.Cascade.All();
Schema("dbo");
Table("PurchaseOrder");
}
}
public class ProductMapping : ClassMap<Product>
{
public ProductMapping()
{
Id(x => x.ProductID, "ProductID");
Map(x => x.Name, "Name");
HasManyToMany(x => x.Orders)
.Table("OrderProducts")
.Schema("dbo")
.ParentKeyColumn("OrderID")
.ChildKeyColumn("ProductID")
.Inverse();
Schema("dbo");
Table("Product");
}
}
答案 1 :(得分:4)
正如其他人所指出的那样,ProductMapping中的父键和子键错误。由于连接,您的查询会返回多个结果。您需要使用转换器仅返回不同的根实体:
var orderList = session.QueryOver<PurchaseOrder>()
.JoinQueryOver<Product>(o => o.Products)
.TransformUsing(Transformers.DistinctRootEntity)
.List();
请注意,如果您只想提取产品集合,可以使用Fetch指定:
var orderList = session.QueryOver<PurchaseOrder>()
.Fetch(o => o.Orders).Eager
.TransformUsing(Transformers.DistinctRootEntity)
.List();
答案 2 :(得分:1)
查看this example,我看到您的ProductMapping
应该交换ParentKeyColumn
和ChildKeyColumn
值,例如:
HasManyToMany(x => x.Orders)
.Table("OrderProducts")
.Schema("dbo")
.ParentKeyColumn("ProductID")
.ChildKeyColumn("OrderID")
.Inverse();
我对这种情况下.Inverse()
的使用存在疑问。我敢打赌它只是告诉NHibernate ProductMapping
不对这种关系负责(虽然不确定)。
答案 3 :(得分:0)
Order
的映射是正确的,您必须仅更改Product
交换的映射:
.ParentKeyColumn("ProductID") // product is the Parent
.ChildKeyColumn("OrderID") // order is the child
关于你需要的Inverse
部分?