为了使自己清楚,我创建了一个最基本的案例来描述我的问题。假设我有3个表:
CREATE TABLE [dbo].[Product](
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED ( [ProductID] ASC )
) ON [PRIMARY]
CREATE TABLE [dbo].[OrderHeader](
[HeaderID] [int] IDENTITY(1,1) NOT NULL,
[Comment] [varchar](100) NULL,
CONSTRAINT [PK_OrderHeader] PRIMARY KEY CLUSTERED ( [HeaderID] ASC )
) ON [PRIMARY]
CREATE TABLE [dbo].[OrderDetail](
[HeaderID] [int] NOT NULL, /* FK to OrderHeader table */
[ProductID] [int] NOT NULL, /* FK to Product table */
[CreatedOn] [datetime] NOT NULL,
CONSTRAINT [PK_OrderDetail] PRIMARY KEY CLUSTERED
(
[HeaderID] ASC,
[ProductID] ASC
)
) ON [PRIMARY]
我已经创建了相应的实体类和映射类。
public class Product {
public virtual int? Id { get; set; }
public virtual string Name { get; set; }
}
public class ProductMap : ClassMap<Product> {
public ProductMap() {
Table("Product");
Id(x => x.Id, "ProductID").GeneratedBy.Identity();
Map(x => x.Name, "ProductName");
}
}
public class OrderHeader {
public virtual int? Id { get; set; }
public virtual string Comment { get; set; }
public virtual IList<OrderDetail> Details { get; set; }
}
public class OrderHeaderMap : ClassMap<OrderHeader> {
public OrderHeaderMap() {
Table("OrderHeader");
Id(x => x.Id, "HeaderID").GeneratedBy.Identity();
Map(x => x.Comment, "Comment");
HasMany<OrderDetail>(x => x.Details)
.KeyColumn("HeaderID")
.Inverse()
.Cascade
.All();
}
}
public class OrderDetail {
public virtual OrderHeader OrderHeader { get; set; }
public virtual Product Product { get; set; }
public virtual DateTime? CreatedOn { get; set; }
public override bool Equals(object obj) {
OrderDetail other = obj as OrderDetail;
if (other == null) {
return false;
} else {
return this.Product.Id == other.Product.Id && this.OrderHeader.Id == other.OrderHeader.Id;
}
}
public override int GetHashCode() {
return (OrderHeader.Id.ToString() + "|" + Product.Id.ToString()).GetHashCode();
}
}
public class OrderDetailMap : ClassMap<OrderDetail> {
public OrderDetailMap() {
Table("OrderDetail");
CompositeId()
.KeyReference(x => x.Product, "ProductID")
.KeyReference(x => x.OrderHeader, "HeaderID");
References<OrderHeader>(x => x.OrderHeader, "HeaderID").ForeignKey().Not.Nullable().Fetch.Join();
References<Product>(x => x.Product, "ProductID").ForeignKey().Not.Nullable();
Version(x => x.CreatedOn).Column("CreatedOn").Generated.Always();
}
}
我还创建了NH Session Provider
public class NHibernateSessionProvider {
private static ISessionFactory sessionFactory;
public static ISessionFactory SessionFactory {
get {
if (sessionFactory == null) {
sessionFactory = createSessionFactory();
}
return sessionFactory;
}
}
private static ISessionFactory createSessionFactory() {
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ShowSql()
.ConnectionString(c => c.FromConnectionStringWithKey("TestDB")))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<OrderHeaderMap>())
.BuildSessionFactory();
}
}
还创建了NH存储库类
public class NHibernateRepository<T, TId> {
protected ISession session = null;
protected ITransaction transaction = null;
public NHibernateRepository() {
this.session = NHibernateSessionProvider.SessionFactory.OpenSession();
}
public void Save(T entity) {
session.SaveOrUpdate(entity);
}
public void AddNew(T entity) {
session.Save(entity);
}
public void BeginTransaction() {
transaction = session.BeginTransaction();
}
public void CommitTransaction() {
transaction.Commit();
closeTransaction();
}
public void RollbackTransaction() {
transaction.Rollback();
closeTransaction();
closeSession();
}
private void closeTransaction() {
transaction.Dispose();
transaction = null;
}
private void closeSession() {
session.Close();
session.Dispose();
session = null;
}
public void Dispose() {
if (transaction != null) {
CommitTransaction();
}
if (session != null) {
session.Flush();
closeSession();
}
}
}
在我的代码中,我创建了两种不同的方法来使用composite-id保存这个主/详细信息结构。
private static void method1() {
NHibernateRepository<Product, int?> repoProduct = new NHibernateRepository<Product, int?>();
NHibernateRepository<OrderHeader, int?> repo = new NHibernateRepository<OrderHeader, int?>();
OrderHeader oh = new OrderHeader();
oh.Comment = "Test Comment " + DateTime.Now.ToString();
oh.Details = new List<OrderDetail>();
for (int i = 0; i < 2; i++) {
oh.Details.Add(new OrderDetail
{
OrderHeader = oh,
Product = repoProduct.GetById(i + 3)
});
}
repo.AddNew(oh);
}
private static void method2() {
NHibernateRepository<OrderHeader, int?> repoHeader = new NHibernateRepository<OrderHeader, int?>();
OrderHeader oh = new OrderHeader();
oh.Comment = "Test Comment " + DateTime.Now.ToString();
repoHeader.Save(oh);
NHibernateRepository<OrderDetail, int?> repoDetail = new NHibernateRepository<OrderDetail, int?>();
for (int i = 0; i < 2; i++) {
OrderDetail od = new OrderDetail
{
OrderHeaderId = oh.Id,
OrderHeader = oh,
ProductId = i + 3,
Product = new Product
{
Id = i + 3
},
};
repoDetail.AddNew(od);
}
}
但是对于这两种方法,从不保存OrderDetail表。我已经打开ShowSql()来查看在控制台上执行的SQL语句,根本没有生成SQL来保存OrderDetail表。
我到处都进行了很多搜索,但无法得出明确的结论。
任何人都有一些线索,我需要做些什么来保存具有复合ID的实体?
由于
哈迪
答案 0 :(得分:3)
模型和映射都不正确。
从 OrderDetail 中删除 OrderHeaderId 和 ProductId 。
然后,Composite ID应该包含 OrderHeader 和 Product 作为参考(我认为Fluent是KeyReference
而不是KeyProperty
;在XML中它是key-many-to-one
而不是key-property
)
然后,添加适当的Cascade
设置,如Cole建议的那样。
样本用法:
using (var session = GetSessionFromSomewhere())
using (var tx = session.BeginTransaction())
{
var orderHeader = new OrderHeader();
...
orderHeader.Details.Add(new OrderDetail
{
OrderHeader = orderHeader;
Product = session.Load<Product>(someProductId);
});
session.Save(orderHeader);
tx.Commit();
}
该块中的所有内容都是必需。
答案 1 :(得分:2)
我认为复合ID不会导致问题。我认为这是您在OrderHeader地图中映射OrderDetails的方式。
我认为它应该是这样的:
HasMany<OrderDetail>(x => x.Details).KeyColumn("HeaderID").Inverse().Cascade.AllDeleteOrphan();
修改强>
您应该听下面的Diego并将您的映射更改为:
public class OrderDetailMap : ClassMap<OrderDetail> {
public OrderDetailMap() {
Table("OrderDetail");
CompositeId()
.KeyReference(x => x.Product, "ProductID")
.KeyReference(x => x.OrderHeader, "HeaderID");
Version(x => x.CreatedOn).Column("CreatedOn").Generated.Always();
}
}
您上面的OrderDetails映射中的代码是导致错误“此SqlParameterCollection的索引2无效且Count = 2”的原因。
References<OrderHeader>(x => x.OrderHeader, "HeaderID").ForeignKey().Not.Nullable().Fetch.Join();
References<Product>(x => x.Product, "ProductID").ForeignKey().Not.Nullable();
答案 2 :(得分:1)
首先,您的OrderDetail映射错误:您可能不会多次映射一列。在这里,您既可以为复合ID分配它,也可以分配多对一。您的composite-id可以(并且应该)具有2个多对一属性,而不仅仅是value属性。
这在您对Diego的回答的最后评论中很明显,另请参阅IndexOutOfRangeException Deep in the bowels of NHibernate
其次你在inverse
集合上设置OrderHeader.Details
,如果我没记错的话,意味着method1不会导致OrderDetail上的插入