5
votes

I'm getting an error with NHibernate when I try to perfrom a ISession.Delete on any table with a One to Many relationship.

NHibernate is trying to set the foreign key to the parent table in the child table to null, rather than just deleting the child table row.

Here is my domain:

public class Parent
{

    public Parent()
    {
        _children = new List<Child>();
    }

    public int Id { get; set; }
    public string SimpleString { get; set; }
    public DateTime? SimpleDateTime { get; set; }

    private IList<Child> _children;
    public IEnumerable<Child> Children
    {
        get { return _children; }
    }
    public void AddChild(Child child)
    {
        child.Parent = this;
        _children.Add(child);
    }
}


public class Child
{
    public int Id { get; set; }
    public string SimpleString { get; set; }
    public DateTime? SimpleDateTime { get; set; }
    [JsonIgnore]
    public Parent Parent { get; set; }
}

I have set-up the Fluent NHibernate mappings as follows:

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        Not.LazyLoad();
        Id(x => x.Id);
        Map(x => x.SimpleString);
        Map(x => x.SimpleDateTime);

        HasMany(x => x.Children)
            .Not.LazyLoad()
            .KeyColumn("ParentId").Cascade.AllDeleteOrphan()
            .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);

    }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        Not.LazyLoad();
        Id(x => x.Id);
        Map(x => x.SimpleString);
        Map(x => x.SimpleDateTime);
        References(x => x.Parent).Not.Nullable().Column("ParentId").Cascade.All().Fetch.Join();
    }
}

I've told NHibernate to Cascade.AllDeleteOrphan() but it's still trying to set the ParentId foriegn key to null here is the test I setup:

public void Delete_GivenTableWithChildren_WillBeDeletedFromDB()
{
    int id;
    using (var createSession = MsSqlSessionProvider.SessionFactory.OpenSession())
    {
        var parent = new Parent();
        parent.AddChild(new Child { SimpleString = "new child from UI" });

        using (var trx = createSession.BeginTransaction())
        {
            createSession.Save(parent);
            trx.Commit();
            id = parent.Id;
        }
    }

    using (var firstGetSession = MsSqlSessionProvider.SessionFactory.OpenSession())
    {
        var result = firstGetSession.Get<Parent>(id);
        Assert.IsNotNull(result);
    }

    using (var deleteSession = MsSqlSessionProvider.SessionFactory.OpenSession())
    {
        using (var trx = deleteSession.BeginTransaction())
        {
            deleteSession.Delete("from " + typeof(Parent).Name + " o where o.Id = :Id", id, NHibernateUtil.Int32);
            trx.Commit();
        }
    }

    using (var session = MsSqlSessionProvider.SessionFactory.OpenSession())
    {
        var result = session.Get<Parent>(id);
        Assert.IsNull(result);
    }
}

Which is failing on the deleteSession.Delete line after attempting the following SQL:

exec sp_executesql N'UPDATE [Child] SET ParentId = null WHERE ParentId = @p0',N'@p0 int',@p0=5

with:

NHibernate.Exceptions.GenericADOException : could not delete collection: [SaveUpdateOrCopyTesting.Parent.Children#5][SQL: UPDATE [Child] SET ParentId = null WHERE ParentId = @p0]
  ----> System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'ParentId', table 'SaveUpdateCopyTestingDB.dbo.Child'; column does not allow nulls. UPDATE fails.
The statement has been terminated.

Does anyone know what I've done wrong in my mappings, or know of a way to stop NHibernate from attempting to null the foreign key id?

Thanks

Dave

2

2 Answers

19
votes

Try setting .Inverse() on the ParentMap's HasMany, so it looks like:

HasMany(x => x.Children)
        .Not.LazyLoad()
        .KeyColumn("ParentId").Cascade.AllDeleteOrphan().Inverse()
        .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
0
votes

I'm not sure if cascade works if you delete with HQL.

Try this:

var parent = deleteSession.Load<Parent>(id)
deleteSession.Delete(parent);

It's a pity that you don't have lazy loading. Load would not need the entity to be read from the database, it would just create a proxy in memory.