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

时间:2013-10-03 17:05:30

标签: c# nhibernate fluent-nhibernate

我正在使用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");
    }
}

QueryOver

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 个答案:

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

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部分?