Handle Concurrency with Transaction in Entity Framework while saving collection - c#

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;
}

Related

Entity framework update related entities without concurrency conflict

I have two related entities: User and UserProfile. A user can have many profiles (settings). I want to be able to update them together, but I am currently getting concurrency error when i do so:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
This is my code to update:
public void UpdateUser(UserList user, int timeoutMins)
{
using (var ctx = GetCodingContext())
{
try
{
ctx.Entry(user).State = System.Data.Entity.EntityState.Modified;
CR_USER_PROFILE timeoutProfile = GetTimeoutUserProfile(user.UserGUID);
if (timeoutProfile != null && !timeoutProfile.PROFILE_VALUE.Equals(timeoutMins.ToString()))
{
timeoutProfile.PROFILE_VALUE = timeoutMins.ToString();
UpdateUserProfile(timeoutProfile,ctx);
}
else if(timeoutProfile == null && timeoutMins > 0)
{
var timeoutKey = FFCEnumerations.Profiles.Keys.Timeout.GetStringValue();
AddUserProfile(user, timeoutKey, timeoutMins.ToString(), ctx);
}
ctx.SaveChanges();
}
catch (Exception ex)
{
throw new Exception("Error occurred updating user " + ex);
}
}
}
public void UpdateUserProfile(CR_USER_PROFILE profile, CodingContext ctx)
{
try
{
ctx.Entry(profile).State = System.Data.Entity.EntityState.Modified;
}
catch (Exception)
{
throw new Exception("Error occurred updating User Profile");
}
}
public CR_USER_PROFILE GetTimeoutUserProfile(Guid userGuid)
{
using (var ctx = GetCodingContext())
{
var timeoutKey = FFCEnumerations.Profiles.Keys.Timeout.GetStringValue();
var profileList = ctx.CR_USER_PROFILE.Where(p => p.UserGUID == userGuid && p.PROFILE_TYPE_CD == timeoutKey);
return profileList.SingleOrDefault();
}
}
It works well when I add both entities, but not when updating. Any ideas?
I think this is where there's a lot of discussion on this problem - Entity Framework: "Store update, insert, or delete statement affected an unexpected number of rows (0)."
I figured out that I was using a different context for fetching the profile I wanted to update. This was causing the concurrency conflict because EF thought this entity was being changed somewhere else (another context). So, I created an overload for this method so I can pass the context as an argument and fetch the entity with the same context I was going to update it with.
public CR_USER_PROFILE GetTimeoutUserProfile(Guid userGuid, CodingContext ctx)
{
var timeoutKey = FFCEnumerations.Profiles.Keys.Timeout.GetStringValue();
var profileList = ctx.CR_USER_PROFILE.Where(p => p.UserGUID == userGuid && p.PROFILE_TYPE_CD == timeoutKey);
return profileList.SingleOrDefault();
}

Entity Framework: Atomic Transaction (Database context)

i am dealing with Entity framework 4 as the application is already been built and i have to make some up gradation in it.
Scenario:
Implemented a DBTransaction(inserts data in database) in my code and once a transaction aborts in the mid way and roll back executes then on next time when same transaction executes with correct/validated data still the transaction abort by giving the previous exception. It is quite difficult to understand as i presume that the RollBack should remove the validation messages and data from the Database Context as it is SQL.
Note: I am using a static DatabaseContext all through.
public class TestClass
{
static SampleDataBaseEntities ctx = new SampleDataBaseEntities();
public void SqlTransaction()
{
ctx.Connection.Open();
using (DbTransaction transaction = ctx.Connection.BeginTransaction())
{
try
{
Student std = new Student();
std.first_name = "first";
//std.last_name = "last"; (This is responsible for generating the exception)
AddTeacher();
ctx.AcceptAllChanges();
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
}
finally
{
ctx.Connection.Close();
}
}
}
public void SqlTransaction2()
{
ctx.Connection.Open();
using (DbTransaction transaction = ctx.Connection.BeginTransaction())
{
try
{
Student std = new Student();
std.first_name = "first";
std.last_name = "last";
AddTeacher();
ctx.Students.AddObject(std);
ctx.SaveChanges(false);
transaction.Commit();
ctx.AcceptAllChanges();
}
catch (Exception e)
{
transaction.Rollback();
transaction.Dispose();
ctx.Connection.Close();
}
}
}
public void AddTeacher()
{
Teacher t = new Teacher();
t.first_name = "teacher_first";
t.last_name = "teacher_last";
t.school_name = "PUCIT";
ctx.Teachers.AddObject(t);
ctx.SaveChanges(false);
}
}
class Program
{
static void Main(string[] args)
{
TestClass test = new TestClass();
test.SqlTransaction();
test.SqlTransaction2();
}
}
Solutions(Which i have tried):
Using the SaveChanges(false).
Using SaveChanges(false) and ctx.AcceptAllChanges().
Workaround:
The workaround which i got is to re instantiate the DatabaseContext object.
As i have complexity issues while re instantiating the context that's why looking for a more appropriate solution.
Thanks in advance.
All problems come from not creating new instances of the context. Simplify your code to this and it should work.
using (var ctx = new SampleDataBaseEntities()) {
Student std = new Student();
std.first_name = "first";
std.last_name = "last";
ctx.Student.Add(std);
ctx.SaveChanges();
}
"The workaround which i got is to re instantiate the DatabaseContext object."
yes this is correct for doing the transaction again.
Since you are using the static data context, so for the next time you do transaction the same data context is used that cause you problem re entering-data and validation errors.
Solution: Try never to use static dataContext since you are doing transactions very sooner. So you need updated datacontext for each transaction. so always try to instantiate a new dataContext and destroy it as soon as your transaction completes.
Hope it will work!

.Net mvc EF codefirst how to hanle concurrent update requests to database

I've got a table in database:
USERID MONEY
______________
1 500
The money value could be changed only by logged in user that owns account. I've got a function like:
bool buy(int moneyToSpend)
{
var moneyRow = db.UserMoney.Find(loggedinUserID);
if(moneyRow.MONEY < moneyToSpend)
return false;
//code for placing order
moneyRow.MONEY -= moneyToSpend;
return true;
}
I know that mvc sessions are always synchronous, so there will never be 2 symulateous calls to this function in one user session. But what if user logs in to the site 2 times from different browsers? Will it be still single threaded session or I can get 2 concurrent requests to this function?
And if there will be concurrency then how should I handle it with EF? Normally in ADO I would use MSSQL's "BEGIN WORK" for this type of situation, but I have no idea on how to make it with EF.
Thank you for your time!
I would suggest you to use RowVersion to handle concurrent requests.
Good reference here: http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
// in UserMoney.cs
[Timestamp]
public byte[] RowVersion { get; set; }
// in model builder
modelBuilder.Entity<UserMoney>().Property(p => p.RowVersion).IsConcurrencyToken();
// The update logic
public bool Buy(int moneyToSpend, byte[] rowVersion)
{
try
{
var moneyRow = db.UserMoney.Find(loggedinUserID);
if(moneyRow.MONEY < moneyToSpend)
{
return false;
}
//code for placing order
moneyRow.MONEY -= moneyToSpend;
return true;
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var submittedUserMoney = (UserMoney) entry.Entity;
var databaseValue = entry.GetDatabaseValues();
if (databaseValue == null)
{
// this entry is no longer existed in db
}
else
{
// this entry is existed and have newer version in db
var userMoneyInDb = (UserMoney) databaseValue.ToObject();
}
}
catch (RetryLimitExceededException)
{
// probably put some logs here
}
}
I do not think it would be a major problem for you since the idea is that MSSQL as far as i know will not allow asyncroneus data commits to the same user from the same thread it has to finish one process before moving to the next one but you can try something like this
using (var db = new YourContext())
{
var moneyRow = db.UserMoney.Find(loggedinUserID);
moneyRow.MONEY -= moneyToSpend;
bool saveFailed;
do
{
saveFailed = false;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
More can be found here Optimistic Concurrency Patterns

SaveChanges() from an EntityFramework context fails silently

I am using an Entity Framework 6.1 Model from Database 'wizard' setup.
When I create a Business object from my context and then try to add for attachment and then SaveChanges() nothing happens. Is there a tracing mode? or something I can turn on to see what is really happened under the covers.
Simple example:
var fb = _context.Business.Create();
//fb.Id exists and is an int but it is auto incr in the db
fb.Name = ub.ACCOUNT_NAME;
fb.ServiceManager = ub.SERVICE_MANAGER;
fb.AccountManager = ub.ACCOUNT_MANAGER;
fb.SalesPerson = ub.SALESPERSON;
fb.Created = DateTime.UtcNow;
fb.Updated = DateTime.UtcNow;
_context.Add(fb);
_context.SaveChanges();
The best way I have found to catch EF errors is by overriding the SaveChange method like below. If you have a centered place to recover logs (like log4net), the function will be able to insert it there.
public partial class Business
{
/// <summary>Override the SaveChange to return better error messages</summary>
public override int SaveChanges()
{
try {
return base.SaveChanges();
}
catch (System.Data.Entity.Validation.DbEntityValidationException ex) {
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Add some logging with log4net here
// Throw a new DbEntityValidationException with the improved exception message.
throw new System.Data.Entity.Validation.DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Have you tried checking for any validation errors?
Here is the try block and validation method I am using in one of my new classes, so treat it as a code sample and not a 100% tested solution as I am still putting together some unit tests:
public List<string> ValidationErrorList = new List<string>();
try
{
_context.SaveChanges();
}
catch (Exception)
{
GetErrors(_context);
}
private void GetErrors(System.Data.Entity.DbContext context)
{
IEnumerable<System.Data.Entity.Validation.DbEntityValidationResult> ve;
ve = context.GetValidationErrors();
ValidationErrorList.Clear();
foreach (var vr in ve)
{
if (vr.IsValid == false)
{
foreach (var e in vr.ValidationErrors)
{
var errorMessage = e.PropertyName.Trim() + " : " +
e.ErrorMessage;
ValidationErrorList.Add(errorMessage);
}
}
}
}
While the above sample only calls the GetErrors method when an exception is triggered, you might also want to try calling it right after the SaveChanges() to see if there are validation errors that are not throwing an exception.
Have you tried creating a new Business object and adding it in? instead of creating one first?
var fb = new Business();
//fb.Id exists and is an int but it is auto incr in the db
fb.Name = ub.ACCOUNT_NAME;
fb.ServiceManager = ub.SERVICE_MANAGER;
fb.AccountManager = ub.ACCOUNT_MANAGER;
fb.SalesPerson = ub.SALESPERSON;
fb.Created = DateTime.UtcNow;
fb.Updated = DateTime.UtcNow;
_context.Business.Add(fb);
_context.SaveChanges();

How to rollback a transaction in Entity Framework

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();
}
}
}

Categories

Resources