流畅的 NHibernate 返回具有多对多映射的重复项
本文关键字:映射 NHibernate 返回 | 更新日期: 2023-09-27 18:33:43
我正在使用带有流利映射的 NHibernate,当我针对多对多关系加入时遇到重复条目的问题。 我下面的简单示例有两个类,采购订单和产品。 一个采购订单可以有多个产品,一个产品可以是许多采购订单的一部分。
当我尝试检索采购订单及其产品时,我会收到对每个产品重复的相同采购订单。 (因此,如果一个采购订单有 5 个产品,我将在我的结果中看到相同的采购订单 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();
我希望订单列表有 2 个采购订单,但实际上有 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 个产品?
正如其他人指出的那样,您在产品映射中的父键和子键是错误的。由于联接,查询将返回多个结果。您需要使用转换器仅返回不同的根实体:
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();
出于某种原因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");
}
}
查看此示例,我看到您的ProductMapping
应该交换ParentKeyColumn
和ChildKeyColumn
值,例如:
HasManyToMany(x => x.Orders)
.Table("OrderProducts")
.Schema("dbo")
.ParentKeyColumn("ProductID")
.ChildKeyColumn("OrderID")
.Inverse();
在这种情况下,我对.Inverse()
的使用表示怀疑。我敢打赌,它只是告诉NHibernate ProductMapping
不对这种关系负责(虽然不确定这一点(。
Order
的映射是正确的,您必须仅更改Product
交换的映射,如下所示:
.ParentKeyColumn("ProductID") // product is the Parent
.ChildKeyColumn("OrderID") // order is the child
关于Inverse
部分,为什么你需要它?