Here's an example that does what I think you're trying to do. Let me know if I've misunderstood what you're trying to do.
Given the following "domain" classes:
public class Person
{
private IList<Pet> pets;
protected Person()
{ }
public Person(string name)
{
Name = name;
pets = new List<Pet>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IEnumerable<Pet> Pets
{
get { return pets; }
}
public virtual void AddPet(Pet pet)
{
pets.Add(pet);
}
public virtual void RemovePet(Pet pet)
{
pets.Remove(pet);
}
}
public class Pet
{
protected Pet()
{ }
public Pet(string name)
{
Name = name;
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
With the following mapping:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
LazyLoad();
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
HasMany(x => x.Pets)
.Cascade.AllDeleteOrphan()
.Access.AsLowerCaseField()
.SetAttribute("lazy", "false");
}
}
public class PetMap : ClassMap<Pet>
{
public PetMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
This test:
[Test]
public void CanDeleteChildren()
{
Person person = new Person("joe");
Pet dog = new Pet("dog");
Pet cat = new Pet("cat");
person.AddPet(dog);
person.AddPet(cat);
Repository.Save(person);
UnitOfWork.Commit();
CreateSession();
UnitOfWork.BeginTransaction();
Person retrievedPerson = Repository.Get<Person>(person.Id);
Repository.Evict(retrievedPerson);
retrievedPerson.Name = "Evicted";
Assert.AreEqual(2, retrievedPerson.Pets.Count());
retrievedPerson.RemovePet(retrievedPerson.Pets.First());
Assert.AreEqual(1, retrievedPerson.Pets.Count());
Repository.Save(retrievedPerson);
UnitOfWork.Commit();
CreateSession();
UnitOfWork.BeginTransaction();
retrievedPerson = Repository.Get<Person>(person.Id);
Assert.AreEqual(1, retrievedPerson.Pets.Count());
}
runs and generates the following sql:
DeletingChildrenOfEvictedObject.CanDeleteChildren : Passed
NHibernate: INSERT INTO [Person] (Name, Id) VALUES (@p0, @p1); @p0 = 'joe', @p1 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: INSERT INTO [Pet] (Name, Id) VALUES (@p0, @p1); @p0 = 'dog', @p1 = '464e59c7-74d0-4317-9c22-9bf801013abb'
NHibernate: INSERT INTO [Pet] (Name, Id) VALUES (@p0, @p1); @p0 = 'cat', @p1 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: UPDATE [Pet] SET Person_id = @p0 WHERE Id = @p1; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2', @p1 = '464e59c7-74d0-4317-9c22-9bf801013abb'
NHibernate: UPDATE [Pet] SET Person_id = @p0 WHERE Id = @p1; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2', @p1 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: SELECT person0_.Id as Id5_0_, person0_.Name as Name5_0_ FROM [Person] person0_ WHERE person0_.Id=@p0; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: SELECT pets0_.Person_id as Person3_1_, pets0_.Id as Id1_, pets0_.Id as Id6_0_, pets0_.Name as Name6_0_ FROM [Pet] pets0_ WHERE pets0_.Person_id=@p0; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: UPDATE [Person] SET Name = @p0 WHERE Id = @p1; @p0 = 'Evicted', @p1 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: UPDATE [Pet] SET Name = @p0 WHERE Id = @p1; @p0 = 'dog', @p1 = '464e59c7-74d0-4317-9c22-9bf801013abb'
NHibernate: UPDATE [Pet] SET Person_id = null WHERE Person_id = @p0 AND Id = @p1; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2', @p1 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: DELETE FROM [Pet] WHERE Id = @p0; @p0 = '010c2fd9-59c4-4e66-94fb-9bf801013abb'
NHibernate: SELECT person0_.Id as Id5_0_, person0_.Name as Name5_0_ FROM [Person] person0_ WHERE person0_.Id=@p0; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
NHibernate: SELECT pets0_.Person_id as Person3_1_, pets0_.Id as Id1_, pets0_.Id as Id6_0_, pets0_.Name as Name6_0_ FROM [Pet] pets0_ WHERE pets0_.Person_id=@p0; @p0 = 'cd123fc8-6163-42a5-aeeb-9bf801013ab2'
Note the DELETE FROM [Pet]...
so, what you need to be able to do is hand nhibernate a Person object (in this example) with the modified collections and it should be able to determmine what to delete. Make sure you have the Cascade.AllDeleteOrphan() attribute set.
Best Answer
The question is: why do you want to do this? I think it is not much of an optimization if you only update columns that have changed.
Have you got triggers in the database?
If so, you can do the following:
select-before-update="true"
anddynamic-update="true"
in the mapping. This makes NH to perform a query before the update and only updates if it changed, and only the columns that have changed. I'm not sure if it selects for every update, or only if it is not in the session.Merge
instead of update. This does actually the same: it selects the entity from the database, only if it is not already in the session. Also usedynamic-update="true"
. There is also a trade-off:Merge
returns the attached instance if there is already one in the session. So you should always throw away the instance you passed in and work with the instance you go fromMerge
.Actually I wouldn't care about the updated columns. Its most probably faster to update them blindly instead of performing a preceding query.