Concurrency
- Entity Framework supports Optimistic Concurrency by default.
- In the optimistic concurrency, EF saves the entity to the database, in the hope that the same data has not changed since the entity was loaded.
- If it finds that the data has changed, then an exception is thrown and you must resolve the conflict before attempting to save it again.
- To handle optimistic concurrency with an entity, you need to have a
RowVersion
column in the table.
RowVersion
is a datatype in the SQL Server that automatically generates unique binary number, whenever the insert or update operation is performed in a table.
- EF includes a
RowVersion
column in the where clause, whenever you do an update operation.
- If the
RowVersion
value is different than in the where clause then it will throw DbUpdateConcurrencyExection
.
- EF includes a property in the where clause, during the update operation, if the property is marked with the
TimeStamp
attribute.
- The property type is
byte[]
because timestamp is binary in C#.
[Timestamp]
public byte[] RowVersion { get; set; }
- If you prefer to use the fluent API, you can use the
IsConcurrencyToken
method to specify the tracking property.
modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsConcurrencyToken();
Transactions
- Entity Framework automatically wraps all the queries made by the
DbContext.SaveChanges()
method in a single explicit database transaction.
- It will use whatever default transaction isolation level the database engine has been configured to use (READ COMMITTED for SQL Server).
- You can use
Database.BeginTransaction
and Database.UseTransaction
to provide more control over transactions.
using (var db = new MyDbContext(ConnectionString))
{
using (var transaction = db.BeginTransaction(IsolationLevel.RepeatableRead))
// Or: using (var transaction = db.Database.BeginTransaction())
{
try
{
// ...
db.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
}
Database.UseTransaction
allows the DbContext
to use a transaction which was started outside of the Entity Framework.
- An side-effect of manually controlling the transaction is that you are now forcing the connection and transaction to remain open for the duration of the transaction scope.
- There's no built-in way to override the default isolation level used for
AutoCommit
and automatic explicit transactions.
- EF relies on for read queries and the explicit transaction it automatically starts when
SaveChanges()
is called use whatever default isolation level the database engine has been configured with. There's no built-in way to override this isolation level.
- If you'd like to use another isolation level, you must start and manage the database transaction yourself.
- The database connection open by
DbContext
will enroll in an ambient TransactionScope
.
- Alternatively, you can also use the
TransactionScope
class to control the transaction scope and isolation level.
- The database connection that Entity Framework opens will enroll in the ambient
TransactionScope
.
- In practice, and unless you actually need a distributed transaction, you should avoid using
TransactionScope
.
TransactionScope
and distributed transactions in general, are not necessary for most applications and tend to introduce more problems than they solve.
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTransaction = conn.BeginTransaction(IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTransaction;
// ...
using (var db = new MyDbContext(conn, contextOwnsConnection: false))
{
db.Database.UseTransaction(sqlTransaction);
// ...
db.SaveChanges();
}
sqlTransaction.Commit();
}
catch (Exception)
{
sqlTransaction.Rollback();
}
}
}
- Connection Resiliency refers to the ability for EF to automatically retry any commands that fail due to these connection breaks.