I'm trying to get NHibernate to delete a collection issuing either a single delete statement or multiple statements. The problem is that either it generates the delete statements AFTER generating some "get current state" queries, or it just sets the children's parent_id to null and doesn't remove them (and still generates those reads).
Tables: TestP ([id] int not null; [name] nvarchar(50) ) TestC ([id] int not null; [id_p] int null; [other] nvarchar(50) )
Initial data
insert into testp (id, name) values(1,'dad')
delete from testc
insert into testc (id, id_p, other) values(1, 1,'son 1')
insert into testc (id, id_p, other) values(2, 1,'son 2')
Entities
public class Testp
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Testc> Children { get; set; }
}
public class Testc
{
public virtual int Id { get; set; }
public virtual int IdP { get; set; }
public virtual string Other { get; set; }
}
Mappings
public class TestpMap : ClassMapping<Testp>
{
public TestpMap()
{
Schema("dbo");
Lazy(true);
Id(x => x.Id, map => map.Generator(Generators.Assigned));
Property(x => x.Name);
Bag(x => x.Children, mapping =>
{
mapping.Key(k => k.Column("id_p"));
mapping.Cascade(Cascade.DeleteOrphans|Cascade.All);
},
r => r.OneToMany());
}
}
public class TestcMap : ClassMapping<Testc>
{
public TestcMap()
{
Schema("dbo");
Lazy(true);
Id(x => x.Id, map => map.Generator(Generators.Assigned));
Property(x => x.Other);
}
}
Test 1
using (var tx = session.BeginTransaction())
{
var test = session.Query<Testp>().First();
test.Children.Clear();
test.Children.Add(new Testc(){Id=103,Other="nuevo"});
tx.Commit();
}
This generates the following SQL:
/* [expression] */select TOP (1) testp0_.Id as Id1_, testp0_.Name as Name1_ from dbo.Testp testp0_
go
exec sp_executesql N'/* load one-to-many engage5_html_generator.Testp.Children */ SELECT children0_.id_p as id3_1_, children0_.Id as Id1_, children0_.Id as Id0_0_, children0_.Other as Other0_0_ FROM dbo.Testc children0_ WHERE children0_.id_p=@p0',N'@p0 int',@p0=1
go
exec sp_executesql N'/* get current state engage5_html_generator.Testc */ SELECT testc_.Id, testc_.Other as Other0_ FROM dbo.Testc testc_ WHERE testc_.Id=@p0',N'@p0 int',@p0=103
go
exec sp_executesql N'/* insert engage5_html_generator.Testc */ INSERT INTO dbo.Testc (Other, Id) VALUES (@p0, @p1)',N'@p0 nvarchar(4000),@p1 int',@p0=N'nuevo',@p1=103
go
exec sp_executesql N'/* delete one-to-many row engage5_html_generator.Testp.Children */ UPDATE dbo.Testc SET id_p = null WHERE id_p = @p0 AND Id = @p1',N'@p0 int,@p1 int',@p0=1,@p1=1
go
exec sp_executesql N'/* delete one-to-many row engage5_html_generator.Testp.Children */ UPDATE dbo.Testc SET id_p = null WHERE id_p = @p0 AND Id = @p1',N'@p0 int,@p1 int',@p0=1,@p1=2
go
exec sp_executesql N'/* create one-to-many row engage5_html_generator.Testp.Children */ UPDATE dbo.Testc SET id_p = @p0 WHERE Id = @p1',N'@p0 int,@p1 int',@p0=1,@p1=103
go
exec sp_executesql N'/* delete engage5_html_generator.Testc */ DELETE FROM dbo.Testc WHERE Id = @p0',N'@p0 int',@p0=1
go
exec sp_executesql N'/* delete engage5_html_generator.Testc */ DELETE FROM dbo.Testc WHERE Id = @p0',N'@p0 int',@p0=2
(sorry for the wall of text) That is, one select, one insert, three updates and two deletes.
I was expecting one delete and one insert.
Test 2: NHibernate documentation says that you can force it to issue a delete "by discarding (ie. dereferencing) the original collection and returning a newly instantiated collection with all the current elements."
Doing test.Children = new List<Testc>();
as-is results in an exception: "A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance"
Changing cascade options to All (mapping.Cascade(Cascade.All);
) fixes it somewhat. The following SQL is generated:
exec sp_executesql N'/* load one-to-many engage5_html_generator.Testp.Children */ SELECT children0_.id_p as id3_1_, children0_.Id as Id1_, children0_.Id as Id0_0_, children0_.Other as Other0_0_, children0_.id_p as id3_0_0_ FROM dbo.Testc children0_ WHERE children0_.id_p=@p0',N'@p0 int',@p0=1
go
exec sp_executesql N'/* get current state engage5_html_generator.Testc */ SELECT testc_.Id, testc_.Other as Other0_, testc_.id_p as id3_0_ FROM dbo.Testc testc_ WHERE testc_.Id=@p0',N'@p0 int',@p0=102
go
exec sp_executesql N'/* insert engage5_html_generator.Testc */ INSERT INTO dbo.Testc (Other, id_p, Id) VALUES (@p0, @p1, @p2)',N'@p0 nvarchar(4000),@p1 int,@p2 int',@p0=N'nuevo',@p1=1,@p2=102
go
exec sp_executesql N'/* delete one-to-many engage5_html_generator.Testp.Children */ UPDATE dbo.Testc SET id_p = null WHERE id_p = @p0',N'@p0 int',@p0=1
go
exec sp_executesql N'/* create one-to-many row engage5_html_generator.Testp.Children */ UPDATE dbo.Testc SET id_p = @p0 WHERE Id = @p1',N'@p0 int,@p1 int',@p0=1,@p1=102
go
2 selects, 1 insert, 2 updates and NO deletes. This was a 'soft delete'.
Can anyone help me find a working example of this? I've been everywhere in the last 4 days trying to make it work. This is what I want: clearing the collection and adding items should result in one delete and many inserts.
Thank you very much.