【问题标题】:Fluent NHibernate Returning Duplicates with Many to Many MappingsFluent NHibernate 返回具有多对多映射的重复项
【发布时间】:2013-10-03 17:05:30
【问题描述】:

我使用 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 个 PurchaseOrder,但实际上有 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 个产品?

【问题讨论】:

  • 您的映射在两个映射中都转到 .Table("OrderProducts"),这是有意的吗?
  • 这是有意的,除非我误解了某些东西(这绝对是可能的)。 OrderProducts 表是将 PurchaseOrder 映射到 Products 的关系表,它是多对多关系的原因。

标签: c# nhibernate fluent-nhibernate


【解决方案1】:

正如其他人指出的那样,您在 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();

【讨论】:

  • 这是我一直在寻找的答案。我有点惊讶 distinct 不是默认行为,因为 NHibernate 确实知道对象是相同的。但不管怎样。谢谢!
【解决方案2】:

由于某种原因,session.QueryOver&lt;T&gt; 不会返回开箱即用的不同结果,您必须通过结果转换器或 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&lt;T&gt; 接口,默认情况下这个接口实际上会返回一个不同的结果:

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");
    }
}

【讨论】:

  • 我的示例中的 OrderProducts 表是您提到的关系表。使用它,并复制其余的映射,即使使用 Fetch,我仍然会遇到同样的问题。从关系的任何一方来看都是如此。 (QueryOver 或 QueryOver 在各自的列表中都有重复项。)
  • @JoeG 你是对的,如果我获取子项,查询返回 4 个元素而不是 2 个,但这似乎是正常的,因为生成的查询实际上返回 4 行......就做@ 987654328@ 应该解决它...只运行 session.QueryOver().List();给了我 2 个结果,但随后,所有子项都因延迟加载而导致
  • 你的 Distinct() 是一个 LINQ 调用。没有直接的 NHibernate 方法来做同样的事情吗?我知道查询结果是多行,但是如果它足够聪明地识别它们是同一个对象,为什么它不能原生地执行 DISTINCT?似乎使用 LINQ 来做这件事是一种变通方法,而不是直接的解决方案。
  • @JoeG 实际上你必须以不同的方式做到这一点。要么你定义一个 ResultTransformer,要么使用 Distinct,在这两种情况下,结果都会在从数据库中检索到之后进行转换......或者你使用 Nhibernate.Linq session.Query 而不是 session.QueryOver,这会做到由于某种原因自动... session.Query().Fetch(p=>p.Products).ToList();
  • @JoeG 我更新了答案,实际上所有 3 个不同的查询几乎完全相同。只有不同的方法可以获得不同的结果......
【解决方案3】:

查看this example,我看到您的ProductMapping 应该交换ParentKeyColumnChildKeyColumn 值,例如:

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

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

【讨论】:

  • 交换父/子键没有我可以看到的影响。你对逆是正确的。它告诉 NHibernate PurchaseOrder 负责保存(这是有道理的,因为您希望订单添加产品,而不是产品添加订单)。
【解决方案4】:

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

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

关于Inverse 部分,您为什么需要它?

【讨论】:

  • 以任何组合更改父/子顺序似乎对结果没有影响。逆需要“使关系的另一方负责储蓄”。 (订单可以添加产品,但产品不应该添加订单。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多