+1  A: 
Morteza Manavi
For me the documention says SaveChanges returns the number of objects it intends to update, not the number it did update in the store. I verified this by setting a breakpoint at SaveChanges(), changing the value in SQL Server and then resuming.
Peter Meinl
Exactly. And if you can call the SaveChanges() *without* any exception, then EF guarantees that the number of objects intends to be updated, is actually get updated in the store or you'll get an exception.
Morteza Manavi
Sorry, I did not read your first comment properly (even though you higlighted the important part). My initial test case did not use optimistic concurrency, but in my real world scenarios I will enable it in most cases.That leaves the question why the TransactionScope throws throws a deadlock error.
Peter Meinl
No problem. I'll update my code to show how you can *possibly* recover from a ConcurrencyException, that might help you get over with the exception but you still need to understand what exactly caused the deadlock, and the best way for that is to start *SQL Server Profiler* and trace it from there.
Morteza Manavi
The task is: only send an email if I am able to change the state from created to reminded.Thus it does not make sense to force through the SaveChanges when handling the OptimisticConcurrencyException.The error handler should exit if changing the state caused the exception and otherwise retry the whole task (reading and saving). How can I do this if optimistic concurreny is enabled via a RowVersion column and not by state only?
Peter Meinl
Here is my test case: http://cid-661c679ee037f677.office.live.com/self.aspx/.Public/EFEmailInTransaction.zip
Peter Meinl
Thanks for sending your Test Case, I've updated my answer accordingly. Sorry I did not have VB installed on my machine to write it in VB for you, but let me know if you need any help on converting the syntax to VB.
Morteza Manavi
Testing which property caused the ConcurrencyException via Refresh does not look robust to me: A concurrent update could change the values between the first SaveChanges() and Refresh(). The only viable solution seems to be turning on concurrency mode for the state property only. Because SaveChanges does not allow to specify the concurrency mode, it looks like I have to move the State property to separate entity to preserve optimistic concurreny for the other properties. I updated my question sample accordingly. I removed the deadlock by moving the read out of the TransactionScope.
Peter Meinl
I agree, another concurrent process might change the row between the first SaveChanges() and Refresh()), but, this code will still handle that based on your requirement: if the State changes by another process in between, then its new value will be show up as OriginalValues and it will throw out the exception even though it was not the State that cause the Concurrency Exception in the first place. If another property changes (other than State) then it will do another SaveChanges().
Morteza Manavi
@Morteza: Thanks a lot for spending so much time with me on this. I summarized my result of our discussion in my answer. For why I do not like the Refresh(ClientWins)/SaveChanges pattern see http://stackoverflow.com/questions/3725663/handle-entity-framework-optimisticconcurrencyexception
Peter Meinl
You are very welcome :)
Morteza Manavi
A: 

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"

Peter Meinl