NHibernate 通过代码映射和 SQLite 数据库:保存多对一的父子实体,子实体获得空外键

NHibernate with mapping by code and a SQLite database: saving many-to-one parent-child entities, child gets a null foreign key

这个问题的变体已被问及 多次 次回答,答案有很多重叠的细节。我已经尝试了这些答案所建议的许多不同的事情,但是 none 对我来说是有效的。

我有一个带有父 table 和子 table 的 SQLite 数据库。这是一个非常简单的设置。我正在使用 NHibernate 4.0.4 通过代码映射而不是像 .

那样流畅

实体:

public class BillingItem
{
    public virtual int ID { get; set; }
    public virtual string Name { get; set; }
    // ... other properties
    public virtual ICollection<PaymentItem> PaymentItems { get; set; }

    public BillingItem()
    {
        PaymentItems = new List<PaymentItem>();
    }
}

public class PaymentItem
{
    public virtual int ID { get; set; }
    public virtual BillingItem OwningBillingItem { get; set; }
    // ... other properties
}

计费项目映射:

public class BillingItemMapping : ClassMapping<BillingItem> 
{
    public BillingItemMapping()
    {
        Table("BillingItems");
        Lazy(true);
        Id(x => x.ID, map => map.Generator(Generators.Identity));

        Set(x => x.PaymentItems, c =>
            {
                c.Key(k =>
                    {
                        k.Column("ID");
                        k.ForeignKey("BillingItemID");
                    });
                c.Inverse(true);
                c.Cascade(Cascade.None);
            },
            r => r.OneToMany(o => { }));

        Property(x => x.Name);
        // ... other properties
    }
}

付款项映射:

public class PaymentItemMapping  : ClassMapping<PaymentItem> 
{
    public PaymentItemMapping()
    {
        Table("PaymentItems");
        Lazy(true);
        Id(x => x.ID, map => map.Generator(Generators.Identity));

        ManyToOne(x => x.OwningBillingItem, m =>
            {
                m.Column("ID");
                m.Update(false);
                m.Insert(false);
                m.Cascade(Cascade.None);
                m.Fetch(FetchKind.Join);
                m.NotFound(NotFoundMode.Exception);
                m.Lazy(LazyRelation.Proxy);
                m.ForeignKey("BillingItemID");
            });

        Property(x => x.DueDate, map => map.NotNullable(true));
        // ... other properties.
    }
}

存储库:

public void Add(BillingItem toAdd)
{
    using (ISession session = Helpers.NHibernateHelper.OpenSession())
    using (ITransaction tran = session.BeginTransaction())
    {
        session.Save(toAdd);

        foreach (var pi in toAdd.PaymentItems)
        {
            session.Save(pi);
        }

        tran.Commit();
    }
}

业务逻辑:

var bi = new BillingItem()
{
    Name = Guid.NewGuid().ToString(),
    // ... others..
};

var pi = new PaymentItem()
{
    OwningBillingItem = bi,
    DueDate = DateTime.Now.AddDays(3)
    // ... others..
};

bi.PaymentItems.Add(pi);
var repo = new Repository();
repo.Add(bi);

正如 this answer (and this and 和许多其他人所建议的那样,我已确保在 BillingItemMapping 中的 Set(子集合)中设置 Inverse(true)。我还在 PaymentItem 对象中设置了我的双向引用:

OwningBillingItem = bi

BillingItem对象:

bi.PaymentItems.Add(pi);

我觉得我已经按照应有的方式设置了所有其他内容,并且我已经根据其他各种来源的建议修改了许多映射设置。但是,我只是想不通为什么它不起作用。

问题是,我无法获取 PaymentItem 记录上的外键列来保存来自 BillingItem 的 ID。如果我将列设置为不允许空值(这是应该的方式),我会得到一个空约束异常。如果我将它设置为允许空值(用于测试),它只会被设置为空值(很明显)。

我做错了什么?

映射的一部分似乎是错误的,是 set

column
// BillingItemMapping()
Set(x => x.PaymentItems, c =>
{
    // this should refer to column where parent id will be found
    c.Key(k =>
    {
        k.Column("ID");
        k.ForeignKey("BillingItemID");
    });

所以应该是

    c.Key(k =>
    {
        k.Column("BillingItemID");
    });

外键仅适用于 sql 生成器..现在可以跳过

此外,对于我使用级联的每个集合

Set(x => x.PaymentItems, c =>
{
    c.Key(k =>
    {
        k.Column("BillingItemID");
    });
    c.Inverse(true);
    //c.Cascade(Cascade.None);
    c.Cascade(Cascade.All);
},

有了它,我们可以简化调用以坚持所有

using (ISession session = Helpers.NHibernateHelper.OpenSession())
using (ITransaction tran = session.BeginTransaction())
{
    session.Save(toAdd);

    //foreach (var pi in toAdd.PaymentItems)
    //{
    //    session.Save(pi);
    //}

    tran.Commit();
}

(我们仍然需要保留两个引用父子/子父)

最后 - 当我们有级联时 - 我们必须允许 NHibernate 完成它的工作 - 插入和更新该关系

ManyToOne(x => x.OwningBillingItem, m =>
{
    m.Column("ID");
    // this is stopper - DO NOT use it
    //m.Update(false);
    //m.Insert(false);

所以,如果我们要更新和插入,请不要设置Update(false)Insert(false)。那应该可以解决所有问题..

这也可以得到一些启示:

呵呵,你的PaymentItemMapping有点问题,正确的映射应该是这样的:

public class PaymentItemMapping : ClassMapping<PaymentItem> {

    public PaymentItemMapping() {
        Table("PaymentItems");
        Lazy(true);
        Id(x => x.ID, map => map.Generator(Generators.Identity));

        ManyToOne(x => x.OwningBillingItem, m => {
            //Do not map to m.Column("ID");
            m.Column("BillingItemID");
            // BillingItemID can be insert and update
            m.Update(true);
            m.Insert(true);
            m.Cascade(Cascade.None);
            m.Fetch(FetchKind.Join);
            m.NotFound(NotFoundMode.Exception);
            m.Lazy(LazyRelation.Proxy);
            m.ForeignKey("BillingItemID");
        });

        Property(x => x.DueDate, map => map.NotNullable(true));
        // ... other properties.
    }
}