As a result of the discussion with Morteza I answer my question as follows.
SaveChanges returns the number of objects it intends to update, not the number it did update in the store. Thus it must be used together with OptimisticConcurrencyException to determine if the change succeeded. One must consider that other properties than the one intended to change can cause a OptimisticConcurrencyException.
Reading an entity and updating it in the same TransactionScope causes a deadlock.
For my Task "Only send an email if I am able to change the State from created to reminded" I use the following solution:
Split the ApprovalRequest entity in two with a 1:1 association, exit on OptimisticConcurrencyException, send mail in TransactionScope with SaveChanges.
ApprovalRequests
ID (PK)
RequestedBy
...
RowVersion (ConcurrencyMode=Fixed)
ApprovalRequestStates
ApprovalRequest_ID (PK, FK)
State (ConcurrencyMode=Fixed)
Using ctx As New ApprovalEntities
Dim approval = cxt.ApprovalRequests.Where ...
Dim state = ctx.ApprovalRequestStates.
Where(Function(r) r.ApprovalRequest_ID = approval.ID And r.State = "Created"
).FirstOrDefault()
If state Is Nothing Then Exit Sub
state.State = "Reminded"
Threading.Thread.Sleep(3000)
Using scope As New TransactionScope
Try
ctx.SaveChanges()
SendMail()
scope.Complete()
Catch ex As OptimisticConcurrencyException
Exit Try
End Try
End Using
End Using
Beware! Updating a child entity when referencing it via its parent causes a DB update of the parent too - in this case throwing an unwanted OptimisticConcurrencyException. Thus I did not use: ApprovalRequests.ApprovalRequestStates.State = "Reminded"