3

我将 NHibernate 与 Fluent 映射一起使用,并且当我加入多对多关系时遇到重复条目的问题。下面我的简单示例有两个类,PurchaseOrder 和 Product。一个 PurchaseOrder 可以有多个 Products,一个 Product 可以是多个 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 个产品?

4

4 回答 4

5

正如其他人指出的那样,您在 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();
于 2013-10-03T20:58:37.173 回答
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");
    }
}
于 2013-10-03T17:32:27.803 回答
1

这个例子,我看到你ProductMapping应该交换ParentKeyColumnChildKeyColumn价值观,比如:

HasManyToMany(x => x.Orders)
        .Table("OrderProducts")
        .Schema("dbo")
        .ParentKeyColumn("ProductID")
        .ChildKeyColumn("OrderID")
        .Inverse();

我对.Inverse()在这种情况下的用法表示怀疑。我敢打赌,它只是告诉 NHibernateProductMapping不负责这种关系(虽然不确定)。

于 2013-10-03T17:22:28.807 回答
0

的映射Order是正确的,您必须仅更改Product交换的映射,如下所示:

.ParentKeyColumn("ProductID") // product is the Parent
.ChildKeyColumn("OrderID") // order is the child

关于Inverse你为什么需要它的部分?

于 2013-10-03T18:18:45.863 回答