流畅的 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 个产品?

流畅的 NHibernate 返回具有多对多映射的重复项

正如其他人指出的那样,您在产品映射中的父键和子键是错误的。由于联接,查询将返回多个结果。您需要使用转换器仅返回不同的根实体:

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应该交换ParentKeyColumnChildKeyColumn值,例如:

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部分,为什么你需要它?