流利的 NHibernate 多对多与额外的列不插入
Posted
技术标签:
【中文标题】流利的 NHibernate 多对多与额外的列不插入【英文标题】:Fluent NHibernate Many to Many with extra column does not insert 【发布时间】:2013-04-24 20:21:46 【问题描述】:我正在努力使用来自 Fluent Nhibernate Many-to-Many mapping with extra column 的流畅的 nhibernate
我已经复制了映射并编写了我能写的最小的程序……但它不会保存……有人能提供一些见解吗???
public class Product
public int Id get; set;
public string Name get; set;
public IList<Inventory> Inventory get; set;
public Product()
Inventory = new List<Inventory>();
public class Warehouse
public int Id get; set;
public string Name get; set;
public IList<Inventory> Inventory get; set;
public Warehouse()
Inventory = new List<Inventory>();
public class Inventory
public Product Product get; set;
public Warehouse Warehouse get; set;
public bool StockInHand get; set;
// override object.Equals
public override bool Equals(object obj)
if (obj == null || GetType() != obj.GetType())
return false;
var i = obj as Inventory;
return ((i.Product.Id == this.Product.Id)
&& (i.Warehouse.Id == this.Warehouse.Id));
// override object.GetHashCode
public override int GetHashCode()
return 9999;
public class ProductMap : ClassMap<Product>
public ProductMap()
Not.LazyLoad();
Table("Product");
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
HasMany(x => x.Inventory).AsBag()
.Cascade.All()
//.Inverse()
.Table("Inventory");
public class WarehouseMap : ClassMap<Warehouse>
public WarehouseMap()
Not.LazyLoad();
Table("Warehouse");
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
HasMany(x => x.Inventory).AsBag()
.Cascade.All()
.Inverse()
.Table("Inventory");
public class InventoryMap : ClassMap<Inventory>
public InventoryMap()
Not.LazyLoad();
Table("Inventory");
CompositeId()
.KeyReference(x => x.Product, "Product_id")
.KeyReference(x => x.Warehouse, "Warehouse_id");
Map(x => x.StockInHand);
还有程序...
using (var session = sessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
Product p = new Product() Id = 1, Name="product" ;
Inventory i = new Inventory() StockInHand = true ;
i.Product = p;
p.Inventory.Add(i);
Warehouse w = new Warehouse() Id = 1, Name = "warehouse" ;
i.Warehouse = w;
w.Inventory.Add(i);
session.SaveOrUpdate(p);
session.Flush();
transaction.Commit();
我得到的例外是
constraint failed\r\nforeign key constraint failed
我还输出了 create 语句,这对我来说是正确的...
create table Inventory (
Product_id INT not null,
Warehouse_id INT not null,
StockInHand BOOL,
primary key (Product_id, Warehouse_id),
constraint FK2B4C61665C5B845 foreign key (Product_id) references Product,
constraint FK2B4C616A6DE7382 foreign key (Warehouse_id) references Warehouse)
create table Product (
Id INT not null,
Name TEXT,
primary key (Id)
)
create table Warehouse (
Id INT not null,
Name TEXT,
primary key (Id)
)
以及在异常之前运行的 SQL....
NHibernate:
INSERT
INTO
Warehouse
(Name, Id)
VALUES
(@p0, @p1);
@p0 = 'warehouse' [Type: String (0)], @p1 = 1 [Type: Int32 (0)]
NHibernate:
INSERT
INTO
Inventory
(StockInHand, Product_id, Warehouse_id)
VALUES
(@p0, @p1, @p2);
@p0 = True [Type: Boolean (0)], @p1 = 1 [Type: Int32 (0)], @p2 = 1 [Type: Int32 (0)]
那么这应该如何正常工作?!?
【问题讨论】:
WarehouseMap
类中出现错误。 Table("Inventory")
不应该类似于 Table("Warehouse")
吗?
@Penfold 我已根据您的评论更新了问题我认为 create table 语句显示了(我认为)在这种情况下我应该期待什么
【参考方案1】:
您的问题的原因是 NHibernate 试图在 Warehouse
记录之前插入 Inventory
记录。这是因为插入的顺序由调用session.Save
的顺序决定。基于此信息,我尝试了许多代码变体来防止外键约束错误。我在下面发布了我最好的解决方案。
using (var session = sessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
var warehouse = new Warehouse() Id = 1, Name = "warehouse" ;
session.Save(warehouse);
var product = new Product() Id = 1, Name = "product";
var inventory = new Inventory
StockInHand = true, Product = product, Warehouse = warehouse;
product.Inventory.Add(inventory);
warehouse.Inventory.Add(inventory);
session.Save(product);
transaction.Commit();
我发现的一件让我相当吃惊的事情是,如果你把session.Save(warehouse)
放在warehouse.Inventory.Add(inventory)
之后,那么NHibernate 不会先插入Warehouse
记录,并且会抛出外键错误。
最后一点,要获得下面列出的三个插入语句,Inverse()
必须在 ProductMap
映射类中重新设置。否则 NHibernate 将发出一个额外的更新语句。
INSERT INTO Warehouse (Name, Id) VALUES (@p0, @p1);@p0 = 'warehouse'
[Type: String (4000)], @p1 = 1 [Type: Int32 (0)]
INSERT INTO Product (Name, Id) VALUES (@p0, @p1);
@p0 = 'product' [Type: String (4000)], @p1 = 1 [Type: Int32 (0)]
INSERT INTO Inventory (StockInHand, Product_id, Warehouse_id) VALUES (@p0, @p1, @p2);
@p0 = True [Type: Boolean (0)], @p1 = 1 [Type: Int32 (0)], @p2 = 1 [Type: Int32 (0)]
【讨论】:
你说的很有道理。问题是 Nhibernate(或者更流畅的 nhibernate)需要确保在插入链接表(每个相邻表都有一个外键链接)之前密钥就位。问题是 Fluent NHibernate 应该计算出这些表需要填充的顺序 - 即在链接表之前。在我的例子中,链接表有额外的数据来描述实体之间的关系。我很乐意接受你的回答,但我认为它不是最理想的(我得到的最佳答案)! 正如我在回答中所说,NHibernate 并没有完全按照我的预期工作。看起来你遇到了一个极端情况。 NHibernate 非常好,但并不完美。抱歉,我不能提供更多帮助。 不,您的回答非常棒 - 反映了我的想法(如果您喜欢阅读,请参阅下文)。也许我错过了一些东西,也许这被悄悄记录在某个地方。我会继续寻找。感谢您的意见。 跑过这个。如果您从逻辑上考虑它,那么在您开始将产品放入其中之前就已经创建了该仓库。 :)【参考方案2】:对于任何可能追随我脚步的人......
我的问题是我想在链接表中存储有关实体之间关系的一些信息。传统上,数据库术语中的多对多关系在域对象之间有一个链接表。
根据问题中概述的结构,需要先插入仓库和产品,然后才能插入库存项目。必须最后插入 Inventory 项目,以便在保存之前两个外键约束都到位。
Insert into Product
Insert into Warehouse
Insert into Inventory (Note this happens **after** the primary keys are
inserted in Warehouse and Product!)
但是,我使用 Fluent NHibernate 生成以下映射...
Insert into Product
Insert into Inventory (Foreign Key constraint violated, no Warehouse)
.. 这一定是不正确的,因为仓库没有主键!我了解问题,但不了解解决方案...我已经设法产生了如下两个解决方案,但我认为两者都不是最佳的。
public class ProductMap : ClassMap<Product>
public ProductMap()
Not.LazyLoad();
Table("Product");
Id(x => x.Id, "Product_id").GeneratedBy.Assigned();
Map(x => x.Name).Column("Name").Length(10);
HasMany(x => x.Inventory)
.Cascade.Delete()
.KeyColumn("Product_id");
public class WarehouseMap : ClassMap<Warehouse>
public WarehouseMap()
Not.LazyLoad();
Table("Warehouse");
Id(x => x.Id, "Warehouse_id").GeneratedBy.Assigned();
Map(x => x.Name).Column("Name").Length(10);
HasMany(x => x.Inventory)
.Cascade.All()
.KeyColumn("Warehouse_id");
public class InventoryMap : ClassMap<Inventory>
public InventoryMap()
Not.LazyLoad();
Table("Inventory");
CompositeId()
.KeyReference(x => x.Product, "Product_id")
.KeyReference(x => x.Warehouse, "Warehouse_id");
Map(x => x.StockInHand);
我可以执行保存并让一半的代码按预期工作,但它以序数为前提。即,我必须了解这些对象需要保存的顺序。我还没有测试过删除会发生什么,但这确实意味着我必须按照正确的顺序来保存这些对象。 /* 工作 */ session.Save(产品); session.Save(仓库); // 也会保存库存
/* would fail */
session.Save(warehouse);
session.Save(product);
或者(我更不喜欢这个)我可以告诉 Nhibernate 我想对所有事情负责......
public class ProductMap : ClassMap<Product>
public ProductMap()
Not.LazyLoad();
Table("Product");
Id(x => x.Id, "Product_id").GeneratedBy.Assigned();
Map(x => x.Name).Column("Name").Length(10);
HasMany(x => x.Inventory).Inverse();
public class WarehouseMap : ClassMap<Warehouse>
public WarehouseMap()
Not.LazyLoad();
Table("Warehouse");
Id(x => x.Id, "Warehouse_id").GeneratedBy.Assigned();
Map(x => x.Name).Column("Name").Length(10);
HasMany(x => x.Inventory).Inverse();
public class InventoryMap : ClassMap<Inventory>
public InventoryMap()
Not.LazyLoad();
Table("Inventory");
CompositeId()
.KeyReference(x => x.Product, "Product_id")
.KeyReference(x => x.Warehouse, "Warehouse_id");
Map(x => x.StockInHand);
现在我得到以下内容
/* works */
session.save(Product);
session.Save(Warehouse);
session.Save(Inventory);
/* works */
session.Save(Warehouse);
session.Save(Product);
session.Save(Inventory);
/* fails */
session.Save(Inventory);
session.Save(Warehouse);
session.Save(Product);
任何人都可以对此进行改进并给我我真正想要的映射,这样我就可以保存一个仓库或一个产品和 Fluent NHibernate,这将使订购正确!?? 例如。
session.Save(warehouse); // or session.Save(product);
这样会导致
Insert into Warehouse...
Insert into Product...
Insert into Inventory... // where NHibernate determines this goes last so that primary keys are in place on both previous tables!
【讨论】:
以上是关于流利的 NHibernate 多对多与额外的列不插入的主要内容,如果未能解决你的问题,请参考以下文章
使用 @IdClass 的 JPA 多对多与额外列在 springTestContextPreparation Hibernate AnnotationException“没有持久的 id 属性”中失