Next feature we are going to implement is updating details of an employee. Possible updates to an employee entity could be changes to properties such as first name, last name, and so on, or addition or removal of one or more benefits or communities. Remember that all updates work in almost the same way so it is more important to understand how updates work than what is being updated.
NHibernate offers multiple different mechanisms for making changes to persistent entities. Before we look into actual implementation of modification of an employee record, I want to spend some time discussing two important concepts that dictate how this feature could be implemented. Clarity around these two concepts should help you determine which mechanism you can use to update entities:
Persistent entities can be updated using transitive persistence feature of NHibernate. To work with detached entities, we can make use of the Update
and Merge
methods available on ISession
. Let's look into both the options in little more detail.
In Chapter 6, Let's Retrieve Some Data from the Database, we discussed transitive persistence. Any changes made to a persistent entity are automatically tracked by session. These changes are synchronized with database either when transaction is committed or session is flushed. Nature of transitive persistence makes it suitable for both partial and full updates. We would implement a partial update where we update residential address of an employee. As with onboarding of an employee, we would add a domain service class where we would implement update employee logic. Following code shows the basic skeleton of that class:
public class UpdateEmplyeeService { public void Execute(int employeeId, Address newAddress) { } }
We have got the Execute
method that takes two parameters. First one is an identifier of the employee entity whose residential address needs to be updated. Second parameter is the new residential address. We want to use transitive persistence to update the residential address. For that, we first need to load the persistent instance of employee from database. As we have done during employee onboarding feature, we would delegate this task to repository. Let's assume that we have a GetById
method available on repository then UpdateEmployeeService
can take a dependency on IRepository<Employee>
and use the repository to load the persistent instance. Once we have persistent instance available then updating any detail is as easy as setting the relevant property to new value. Following is how the complete code looks:
public class UpdateEmplyeeService { private readonly IRepository<Employee> repository; public UpdateEmplyeeService(IRepository<Employee> repository) { this.repository = repository; } public void Execute(int employeeId, Address newAddress) { var employee = repository.GetById(employeeId); employee.ResidentialAddress = newAddress; } }
One important thing to remember here is that the previous mechanism works when the session that loaded the persistent entity is still open while entity is being updated (in other words, the entity being updated has not become detached). Our "session per request" implementation ensures that the session is kept open during processing of the request. Also note that the updates made to the employee
instance are synchronized with database when transaction is committed. In our case, that happens at the last moment towards the end of request processing and hence actual database update happens at the point only.
For the sake of completeness, following is the implementation of the GetById
method on the Repository<T>
class:
public T GetById(int id) { return session.Load<T>(id); }
Note the use of the Load<T>
method instead of the Get<T>
method. If you recall from Chapter 5, Let's Store Some Data into the Database, Load<T>
would not go to database to load the entity but only return an in-memory proxy instance with identifier set to what was passed to the method. On the other hand, Get<T>
would actually hit the database to load the entity. There is no need to load the entity in order to update some properties on it.
We made an assumption that identifier of the entity to be updated is known. We then used the identifier value to retrieve the persistent instance of the entity. Loading the persistent instance and having some method on repository to do is the important point here. You could have used any other property on the previous employee
in place of identifier.
Transitive persistence only works with persistent entities and is capable of performing both partial and full updates. Transitive persistence should be your preferred mechanism for implementing entity updates. We will see why after we look at the other mechanism for updating entities.
NHibernate supports synchronizing changes made to detached entities through two different methods available on ISession
. We have briefly looked at both of these methods in Chapter 6, Let's Retrieve Some Data from the Database, but let's look at them again with little more detail. First method is Merge
. This method takes in a detached instance of an entity, associates it with session, and returns the persistent instance of the entity. Internally, NHibernate checks if the entity being asked to merge exists in session. If it does, then NHibernate will update the entity in session with the property values from the entity being passed. It then returns the entity in session. After this point, because you are dealing with a persistent entity, any changes made to the properties of the entity would be tracked by NHibernate. If the entity was not present in the session, then NHibernate will load the entity from database and then carry out the merge operation. Following code listing shows this in action:
using (var tx = Session.BeginTransaction()) { var emp = new Employee { Id = (int) id, Firstname = "Hillary" }; emp.AddBenefit(new Leave { AvailableEntitlement = 25, RemainingEntitlement = 23, Type = LeaveType.Paid }); var emp2 = Session.Merge(emp); emp2.EmailAddress = "[email protected]"; tx.Commit(); }
Here, we have got an instance of the Employee
class with its identifier set to some value and some other properties set. We pass this instance to the Merge
method which returns another instance of the Employee
class. If a record is present in database with identifier value of original instance, then this new instance represents that record in database. At the same time, state of the entity in the session is overwritten with state of the entity that we passed to the Merge
method. We then updated the EmailAddress
property of the persistent instance. In the end, we commit the transaction, at which point, all the changes made to the persistent entity are synchronized to database. Preceding code would generate the following SQL:
INSERT INTO Benefit (NAME, Description, Employee_Id, Id) VALUES (@p0, @p1, @p2, @p3 ); @p0 = NULL, @p1 = NULL, @p2 = 11, @p3 = 65537 INSERT INTO Leave (Type, AvailableEntitlement, RemainingEntitlement, Id) VALUES (@p0, @p1, @p2, @p3); @p0 = 0, @p1 = 25, @p2 = 23, @p3 = 65537 UPDATE Employee SET Firstname = @p0, Lastname = @p1, EmailAddress = @p2 WHERE Id = @p3; @p0 = 'Hillary', @p1 = NULL, @p2 = '[email protected]', @p3 = 11 DELETE FROM SeasonTicketLoan WHERE Id = @p0; @p0 = 65536 DELETE FROM Benefit WHERE Id = @p0; @p0 = 65536
A major issue with Merge
is that it always attempts to do a full update. What that means is, if you try to merge an entity with only few properties set to a value you want to update to and leave the remaining properties to null, then Merge
will update those null properties as well thinking you intend to update those properties to null values. Following unit test explains this in detail:
[Test] public void PartialUpdatesUsingMergeResultInInconsistentState() { object id = 0; using (var tx = Session.BeginTransaction()) { id = Session.Save(new Employee { Firstname = "John", Lastname = "Smith" }); tx.Commit(); } Session.Clear(); using (var tx = Session.BeginTransaction()) { var emp = new Employee { Id = (int)id, Firstname = "Hillary" }; var emp2 = Session.Merge(emp); emp2.EmailAddress = "[email protected]"; tx.Commit(); } Session.Clear(); using (var tx = Session.BeginTransaction()) { var employee = Session.Get<Employee>(id); Assert.That(employee.Lastname, Is.Null); tx.Commit(); } }
In this test, we first saved an employee instance having only the Firstname
and Lastname
properties set to some value. We then created another instance in-memory having its Firstname
property set to a new value and the Id
property set to the identifier of the previously saved instance. We then merged this new instance by passing it to the Merge
method. Instance returned by the Merge
method, emp2
, is the new persistent instance. We update the EmailAddress
property on this instance hoping that it would be persisted to database. When the changes are synchronized to the database, NHibernate checked that the original entity has Lastname
but merged entity has Lastname
set to null so it generates following SQL statement to update Lastname
to null along with Firstname
and EmailAddress
being updated to correct value.
UPDATE employee SET Firstname = @p0, Lastname = @p1, EmailAddress = @p2 WHERE Id = @p3; @p0 = 'Hillary', @p1 = NULL, @p2 = '[email protected]', @p3 = 11
So use Merge
carefully as it may result in properties set to null inadvertently, resulting in subtle issue and data loss.
We have seen how using detached entities for update operations may lead to subtle defects resulting in data loss at times. The Merge
and Update
methods work perfectly fine only when they are passed with a fully hydrated entity. In other words, they only work reliably in case of full updates. Reality of building software for businesses is that on most occasions we need to deal with partial updates. For instance, in case of employee benefit management system, do you think we would have one screen where all details of an employee including employee's name to her benefits and communities can be updated? I highly doubt that. An elegant user journey would be the one where end user can add a benefit to an employee independently of updating any other details on the employee's profile. In order to facilitate such a use case using detached entities, you would need to pass all details of the employee from UI to backend so that backend can have a fully hydrated employee instance without having to go to database. This is not only a contrived way of fulfilling simple task but is also inefficient. Instead of passing the whole employee profile from UI to backend, why not just pass the identifier and load the detail from database? And if you are loading the employee
instance from database then why not use transitive persistence?