简体   繁体   English

具有Composite-Id保存问题的NHibernate实体

[英]NHibernate Entity with Composite-Id Saving Issue

In order to make myself clear, I have created a most basic case to describe my problem. 为了使自己清楚,我创建了一个最基本的案例来描述我的问题。 Let's say I have 3 tables: 假设我有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]

And I have created correponding entity classes and mapping classes. 我创建了相应的实体类和映射类。

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

I have also created NH Session Provider 我还创建了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();
    }
}

And a NH repository class is also created 还创建了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();
            }
        }
    }

In my code, I have created 2 different ways to save this master/detail structure with composite-id. 在我的代码中,我创建了两种不同的方法来使用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);
        }
    }

But for both methods, the OrderDetail table is never saved. 但是对于这两种方法,从不保存OrderDetail表。 I have turned on ShowSql() to see SQL statement executed on console, no SQL generated to save OrderDetail table at all. 我已经打开ShowSql()来查看在控制台上执行的SQL语句,根本没有生成SQL来保存OrderDetail表。

I did quite a lot of search everywhere and could not have a clear conclusion what is wrong. 我到处做了很多搜索,无法得出明确的结论。

Anybody has some clue, what exactly do I need to do to save an entity with composite-id? 任何人都有一些线索,我需要做什么来保存具有复合ID的实体?

Thanks 谢谢

Hardy 哈迪

Both the model and the mapping are incorrect. 模型和映射都不正确。

Remove OrderHeaderId and ProductId from OrderDetail . OrderDetail中删除OrderHeaderIdProductId

Then, the Composite id should include OrderHeader and Product as references (I think with Fluent it's KeyReference instead of KeyProperty ; in XML it's key-many-to-one instead of key-property ) 然后,Composite id应该包含OrderHeaderProduct作为引用(我认为使用Fluent它的KeyReference而不是KeyProperty ;在XML中它是key-many-to-one而不是key-property

Then, add a proper Cascade setting, as Cole suggested. 然后,添加适当的Cascade设置,如Cole建议的那样。

Sample usage: 样品用法:

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

Everything in that block is required . 该块中的所有内容都是必需的

I don't think that the composite-id is what is causing you issues. 我不认为复合ID是导致问题的原因。 I think it's the way you have your OrderDetails mapped in your OrderHeader map. 我认为这是您在OrderHeader地图中映射OrderDetails的方式。

I think it should be something like this instead: 我认为它应该是这样的:

HasMany<OrderDetail>(x => x.Details).KeyColumn("HeaderID").Inverse().Cascade.AllDeleteOrphan();

Edit: 编辑:

You should listen to Diego below and change your mapping to: 您应该听下面的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();
    }
}

The code you have in your above mapping of OrderDetails is what is causing you the error "Invalid index 2 for this SqlParameterCollection with Count=2." 您上面的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();

well firstly, your OrderDetail is mapped wrongly: You may not map one column multiple times. 首先,您的OrderDetail映射错误:您可能不会多次映射一列。 Here you both assign it for composite-id as well as have a many-to-one. 在这里,您既可以为复合ID分配它,也可以分配多对一。 Your composite-id can (and should) have 2 many-to-one properties and not just value properties. 您的composite-id可以(并且应该)具有2个多对一属性,而不仅仅是value属性。

This is evident in your last comment on Diego's answer, see also IndexOutOfRangeException Deep in the bowels of NHibernate 这在您对Diego的答案的最后评论中很明显,另见IndexOutOfRangeException在NHibernate的深处

Secondly you are setting an inverse on the OrderHeader.Details collection which if i remember correctly means method1 would not cause an insert on the OrderDetail 其次,你在OrderHeader.Details集合上设置了一个inverse ,如果我没记错的话,意味着method1不会在OrderDetail上导致插入

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM