I am now using domain driven design techniques. This keeps me from worrying about database triggers etc. and keeps all the logic within the .Net code. Answer is here so that people can see an alternative.
I treat the parent of the collection of periods as an aggregate root and the root has a timestamp. Aggregate root is a consistency boundaries for transactions, distributions and concurrency (see Evans - what I've learned since the blue book). This is used to check the last time any change was confirmed to the database.
I then have methods to add the hire periods to the parent. I test on overlap when adding the movement to the aggregate root. e.g. AddHire(start,end)
- will validate that this creates no overlap on the in memory domain object.
AS there is no overlap I save the changes (via my repository), and check the database timestamp is still the same as at the start of the process. Assuming timestamp is the same as it was when I retrieved the entity the changes are persisted and the database updates the timestamp.
If someone else tries to save changes when the aggregate root is being worked on then either I will commit first or they will. If I commit first the timestamps will not match and the overlap check will re-run to make sure that they haven't created an overlap in the intervening time.