I would like know what is the best possible way to implement transactions with DBContext. In particular,
Does DbContext.SaveChanges implement transaction internall if i change multiple entities?
If i want to call DbContext.SaveChanges multiple times(same contxet/different contxets), how transaction can be achieved?
Yes. SaveChanges uses transaction internally.
Use TransactionScope to wrap multiple calls to SaveChanges
Example:
using(var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
// Do something
context.SaveChanges();
// Do something else
context.SaveChanges();
scope.Complete();
}
Related
I have a WebAPI2 Restful services API and I am using SQL Server database with Entity Framework. I have PUT methods like this
/*
* This changes the Study Status.
*/
[HttpPut, Route("ResponseSetStatus/{id:int}")]
public IHttpActionResult UpdateResponseSetStatus(int id, [FromUri] string status = null)
{
var db = new MyContext(MyContext.EntityContextString);
var responseSet = db.ResponseSets.FirstOrDefault(x => x.ResponseSetId == id);
if (responseSet == null)
{
return NotFound();
}
// ADD ONE SECOND DELAY HERE FOR TESTING
Thread.Sleep(1000);
responseSet.Status = status;
db.SaveChanges();
return Ok();
}
I thought this would work! But it fails. One of the columns in the database is a rowVersion (to prevent lost updates). When I call this function from multiple clients I get exception...
An exception of type 'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException' occurred in EntityFramework.dll but was not handled in user code
because of rowVersion mismatch. Do I really need an explicit transaction for all my update apis? I thought the framework is supposed to do that for me.
Since no one has answered, I will. Yes, WebAPI2 does not wrap the call in a transaction. That would be silly, if you think about it. Also the code
using (var db = new MyContext()) {
// do stuff
}
does not implicitly create a transaction. Therefore, when you implement a RESTFUL PUT method to update your database, you have three options: (1) call db.SaveChanges() one time only and hope for the best, as the OP code, or (2) you can add a rowVersion column, and call db.SaveChanges() with try-catch in a loop, or (3) you can create an explicit transaction.
In my opinion, option 1 is evil, and option 2 is a terrible hack that was invented because transactions did not exist prior to EF6.
The correct way to implement Update:
[HttpPut, Route("ResponseSetStatus/{id:int}")]
public IHttpActionResult UpdateResponseSetStatus(int id, [FromUri] string status = null)
{
using (var db = new MyContext(MyContext.EntityContextString))
{
using (var tran = db.Database.BeginTransaction())
{
var responseSet = db.ResponseSets.FirstOrDefault(x => x.ResponseSetId == id);
if (responseSet == null)
{
return NotFound();
}
// ADD ONE SECOND DELAY HERE FOR TESTING
Thread.Sleep(1000);
responseSet.Status = status;
tran.Commit();
}
}
return Ok();
}
Please note that try-catch is not necessary. If anything fails, the using tran will automatically rollback, and the WebAPI2 will send a nice 500 response to the client.
p.s. i put the db = new MyContext also in using, because it's the right way to do it.
With 2 different context classes which use the same database what is the best way include changes to both in the same transaction?
You can use TransactionScope for distributed transaction. Concept:
using(var transaction = new TransactionScope())
{
using (var context1 = new DbContext1())
{
...
context1.SaveChanges();
}
using (var context2 = new DbContext2())
{
...
context2.SaveChanges();
}
transaction.Complete();
}
Only when TransactionScope is completed (committed), changes will reflect in database.
I have not had any luck with transactions and entity framework 5. I have the following code:
context.Database.Connection.Open();
transaction = context.Database.Connection.BeginTransaction(IsolationLevel.Serializable);
//some work happens
context.SaveChanges();
//some additional work
context.SaveChanges();
transaction.Commit();
At the very first context.SaveChanges call, I get an exception: "Connection is already part of a local or a distributed transaction"
Right now I am actually just doing a trivial proof of concept where all I am doing is attaching an entity, marking it as modified and then calling save changes.
As a troubleshooting deal, I put in an event handler for when the connection state changes and had a breakpoint in there. Doing that, I verified that the connection did not close on me between when I started the transaction and when I called save changes.
Any help figuring out why it is giving me that exception would be tremendously appreciated.
This is the way we used transactions before. It worked for us:
public void DoSomething()
{
using (var db = GetContext())
{
using (var ts = GetTransactionScope())
{
//do stuff
db.SaveChanges();
ts.Complete();
}
}
}
public TransactionScope GetTransactionScope()
{
var tso = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
return new TransactionScope(TransactionScopeOption.Required, tso);
}
If for some reason you have to do multiple SaveChanges calls in one transaction the recommended way is to wrap them in a TransactionScope:
using(var tran = new TransactionScope())
{
using(var context = new MyContext())
{
//some work happens
context.SaveChanges();
//some additional work
context.SaveChanges();
}
tran.Complete(); // without this call the transaction is rolled back.
}
The default isolation level is serializable. Each connection that is opened within the transaction enlists in this transaction. By default, EF always opens and closes connections when it executes queries.
I guess the cause of this exception you've got is that EF creates a transaction object itself when it executes SaveChanges. It tries to use its connection to start this transaction, but the connection is already part of the transaction you created. By using a TransactionScope, the EF transaction just enlists in the ambient transaction.
Does someone knows how to call a StoredProc using the same transaction of an objectContext SaveChanges method (EntityFramework 5)?
The goal is to apply the objects changes and call a stored Proc that does some "magic" on the DB, but, if something goes wrong (either with the SaveChanges or with the SP execution) no changes would be committed at all.
Steps:
Create the context
get the connection from the context
Create the transaction (TransactionScope)
Open the connection (will enlist the connection into the ambient transaction created in 3. and will prevent from closing the connection by the context)
Do SaveChanges()
Execute your stored procedure
Commit the transaction
Close the connection
Some code (MyContext is derived from DbContext):
using (var ctx = new MyContext())
{
using (var trx = new TransactionScope())
{
var connection = ((IObjectContextAdapter)ctx).ObjectContext.Connection;
try
{
ctx.Entities.Add(new MyEntity() { Number = 123 });
ctx.SaveChanges();
ctx.Database.ExecuteSqlCommand("INSERT INTO MyEntities VALUES(300)");
trx.Complete();
}
finally
{
connection.Close();
}
}
}
I want to invoke a validation function inside the entities objects right before they are stored with ObjectContext#SaveChanges(). Now, I can keep track of all changed objects myself and then loop through all of them and invoke their validation methods, but I suppose an easier approach would be implement some callback that ObjectContext will invoke before saving each entity. Can the latter be done at all? Is there any alternative?
I've figured out how. Basically, we can intercept SavingChanges event of ObjectContext and loop through the newly added/modified entities to invoke their validation function. Here's the code I used.
partial void OnContextCreated()
{
SavingChanges += PerformValidation;
}
void PerformValidation(object sender, System.EventArgs e)
{
var objStateEntries = ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified);
var violatedRules = new List<RuleViolation>();
foreach (ObjectStateEntry entry in objStateEntries)
{
var entity = entry.Entity as IRuleValidator;
if (entity != null)
violatedRules.AddRange(entity.Validate());
}
if (violatedRules.Count > 0)
throw new ValidationException(violatedRules);
}
Well, you could do it that way, but it means that you're allowing your clients to directly access the ObjectContext, and, personally, I like to abstract that away, in order to make the clients more testable.
What I do is use the repository pattern, and do the validation when save is called on a repository.