string[] usersToAdd = new string[] { "asd", "asdert", "gasdff6" };
using (Entities context = new Entities())
{
foreach (string user in usersToAdd)
{
context.AddToUsers(new User { Name = user });
}
try
{
context.SaveChanges(); //Exception thrown: user 'gasdff6' already exist.
}
catch (Exception e)
{
//Roll back all changes including the two previous users.
}
Or maybe this is done automatically, meaning that if error occurs, committing changes are canceled for all the changes.
is it?
OK
I created a sample a application like the example from the the question and afterwords I checked in the DB and no users were added.
Conclusion: ObjectContext.SaveChange it's automatically a transaction.
Note: I believe transactions will be needed if executing sprocs etc.
I believe (but I am no long time expert in EF) that until the call to context.SaveChanges goes through, the transaction is not started. I'd expect an Exception from that call would automatically rollback any transaction it started.
Alternatives (in case you want to be in control of the transaction) [from J.Lerman's "Programming Entity Framework" O'Reilly, pg. 618]
using (var transaction = new System.Transactions.TransactionScope())
{
try
{
context.SaveChanges();
transaction.Complete();
context.AcceptAllChanges();
}
catch(OptimisticConcurrencyException e)
{
//Handle the exception
context.SaveChanges();
}
}
or
bool saved = false;
using (var transaction = new System.Transactions.TransactionScope())
{
try
{
context.SaveChanges();
saved = true;
}
catch(OptimisticConcurrencyException e)
{
//Handle the exception
context.SaveChanges();
}
finally
{
if(saved)
{
transaction.Complete();
context.AcceptAllChanges();
}
}
}
Related
I am using the transaction scope from System.Transactions.
I have this method where I have two insertions in database. The first Localization is inserted, but then rolled back since it fails on the second insertion.
Now the error is not with the data I send. The data is good. When I remove the transaction scope it works.
I get this error:
System.InvalidOperationException: A root ambient transaction was completed before the nested transaction. The nested transactions should be completed first.
It also enters the second catch and disposes the scope. What could be the problem?
This is my code:
public async Task InsertCategory(InsertCategoryRequest request)
{
using var scope = new TransactionScope();
int localizationId;
try
{
localizationId = await _localizationRepository.InsertLocalization(new Localization
{
English = request.NameEN,
Albanian = request.NameAL,
Macedonian = request.NameMK
});
}
catch (Exception e)
{
scope.Dispose();
Log.Error("Unable to insert localization {#Exception}", e);
throw ExceptionHandler.ThrowException(ErrorCode.Localization_UnableToInsert);
}
try
{
await _categoryRepository.InsertCategory(new Category
{
Name = request.NameEN,
LocalizationId = localizationId
});
}
catch (Exception e)
{
scope.Dispose();
Log.Error("Unable to insert category {#Exception}", e);
throw ExceptionHandler.ThrowException(ErrorCode.Category_UnableToInsert);
}
scope.Complete();
scope.Dispose();
}
I found the answer. I looked for such a long time, but after I posted I found the answer lol.
Just added TransactionScopeAsyncFlowOption.Enabled when constructing the Transaction Scope.
I would like to use the same dbContext to save a collection of Program type objects, but if there is any exception or concurrency exception in any of the program object, I would like to rollback the whole saved collection, and need to notify user about all program objects where concurrency issue occurred. I am using Entity Framework 6.1.
Find the code snippet. I am facing an issue that, if any of program object is having concurrency exception then programContext object is throwing the same exception again even if next record is not having any concurrency issue. Please guide on this if it is wrong then how can we achieve it in EF6.1
//Code
public List<ProgramViewModel> SavePrograms(List<ProgramViewModel> newAndUpdatedPrograms)
{
List<ProgramViewModel> failedPrograms = new List<ProgramViewModel>();
using (ProgramContext programContext = new ProgramContext())
{
using (DbContextTransaction dbProgramTransaction = programContext.Database.BeginTransaction())
{
bool isErrorOccured = false;
foreach (var item in newAndUpdatedPrograms)
{
try
{
Program program = new Program();
program.ProgramID = item.ProgramId;
program.Title = item.Title;
program.ProgramCode = item.ProgramCode;
program.Description = item.Description;
//This is to check whether user is having the latest record or dirty record (Concurency check)
program.RowVersion = System.Convert.FromBase64String(item.RowVersion);
if (program.ProgramID == 0)
programContext.Entry(program).State = System.Data.Entity.EntityState.Added;
else
programContext.Entry(program).State = System.Data.Entity.EntityState.Modified;
programContext.SaveChanges(); //Throws the previous concurrency exception here
}
catch (DbUpdateConcurrencyException ex)
{
isErrorOccured = true;
failedPrograms.Add(item);
}
}
if (isErrorOccured)
{
dbProgramTransaction.Rollback();
}
else
{
dbProgramTransaction.Commit();
}
}
}
return failedPrograms;
}
I have below method
public void UpdateQuantity()
{
Sql ss = new Sql();
M3 m3 = new M3();
TransactionOptions ff = new TransactionOptions();
ff.IsolationLevel = IsolationLevel.ReadUncommitted;
using (TransactionScope dd = new TransactionScope(TransactionScopeOption.Required, ff))
{
try
{
ss.AddRegion("ALFKI", "SES1"); //step 1
m3.UpdateAnotherSystem(); //step2
dd.Complete();
}
catch (Exception)
{
}
}
}
public void AddRegion(string customerName, string Deception)
{
using (NorthWind context = new NorthWind())
{
Region rr = new Region();
rr.RegionID = 5;
rr.RegionDescription = "Ssaman";
context.Regions.Add(rr);
try
{
context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
}
In that first im going to update Sql server data base .After that im going to perform another update on other system.If step2 fails(may be network failure) then i need to reverse step 1.There for i put two method calls inside the transactionscope. I'm use entity framework to work with sql.Entity framework always set the transaction isolation level as read committed(according to the sql profiler).
but my problem is after context.SaveChanges() called my target table is locked till transaction completes(dd.Complete()).
Are there are any way to change entity framework transaction isolation level?(My entity framework version is 5).
SQL Server does not release locks that were taken due to writes until the end of the transaction. This is so that writes can be rolled back. You cannot do anything about this.
End your transaction or live with the fact that the rows written are still in use. Normally, this is not a problem. You should probably have a single context, connection and transaction for most work that happens in an HTTP request or WCF request. Transactions do not block on themselves.
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand(
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'"
);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
}
This question already has an answer here:
Integrity of my transaction is lost with "TransactionInDoubtException" exception
(1 answer)
Closed 7 years ago.
Does partial commit happen when TransactionInDoubtException is encountered? If so how to roll back the commits/ properly handle TransactionInDoubtException
var option = new TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadCommitted
using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
{
try
{
Context.SaveEmail(_emailInfoList);
context.SaveSyncState(syncState);
scope.Complete();
return true;
}
catch (TransactionInDoubtException ex)
{
// Looks like commit has already taken place
// How to roll back the changes ??
// sth like scope.Rollback()
}
}
According to MSDN:
This exception is thrown when an action is attempted on a transaction
that is in doubt. A transaction is in doubt when the state of the
transaction cannot be determined. Specifically, the final outcome of
the transaction, whether it commits or aborts, is never known for this
transaction.
This exception is also thrown when an attempt is made to commit the
transaction and the transaction becomes InDoubt.
This is a recoverable error.
EDIT:
For recovery:
You have to catch TransactionInDoubtException& write compensate logic with apt checks.
using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
{
try
{
Context.SaveEmail(_emailInfoList);
context.SaveSyncState(syncState);
scope.Complete();
return true;
}
catch (TransactionInDoubtException ex)
{
//check whether any one record from the batch has been partially committed . If committed then no need to reprocess this batch.
// transaction scope should be disposed first .
scope.Dispose();
if (IsReprocessingNeeded(syncState))
throw;
return true;
}
}
/// <returns></returns>
private bool IsReprocessingNeeded(SyncStateDataModal syncState)
{
while (true)
{
try
{
var id = _emailInfoList[0].ID;
bool isEmailsCommitted = Context.GetJournalEmail().FirstOrDefault(a => a.ID == id) != null;
if (!isEmailsCommitted)
return true;
if (context.EmailSynch(syncState.Id) == null)
{
context.SaveSyncState(syncState);
}
return false;
}
catch (Exception ex)
{
Thread.Sleep(TimeSpan.FromMinutes(AppConfiguration.RetryConnectionIntervalInMin));
}
}
}
SOURCE What is the recovery path for TransactionInDoubtException ?
Please see to example1. If some of the data will be entered incorrectly, EF4 will not survive nor any record.
The question: whether as a force to ignore an error in one record and continue on.
example1:
foreach (var tag in split)
{
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = tag
});
}
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = ExtractDomainNameFromURL(resource.url)
});
try
{
context.SaveChanges();
}
catch (UpdateException ex)
{
}
catch (Exception ex)
{
throw;
}
example2 alternative:
foreach (var tag in split)
{
try
{
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = tag
});
context.SaveChanges();
}
catch (UpdateException ex)
{
}
}
try
{
context.NameToResourcer.AddObject(new NameToResourcer()
{
id_resource = resource.id,
name = ExtractDomainNameFromURL(resource.url)
});
context.SaveChanges();
}
catch (UpdateException ex)
{
}
Context behaves like unit of work. It means that when you modify data and store them with the single call to SaveChanges you are telling EF that you want atomic operation - either all changes are successfully saved or all changes are rolled back. EF use a transaction internally to support this behavior. If you don't want this behavior you cannot save all data with single call to SaveChanges. You must use separate call for each atomic set of data.
One possible solution is to disable validation on saving.But I don't recommend it.
db.Configuration.ValidateOnSaveEnabled = false;