Entity framework update related entities without concurrency conflict - c#

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

Related

Entity Framework Core DbUpdateException detailed information

I am just getting started with EF Core in my full .net 4.5.2 project and am trying to do an integration test to validate I can insert a new student.
The issue is, I want to be able to get better information from the exception being thrown as to why it is not inserting into the database.
Here is my integration test code:
[Fact]
public void save_the_new_student_to_the_database()
{
var fixture = new Fixture();
var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>();
//optionsBuilder.UseInMemoryDatabase();
optionsBuilder.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = afmil_Test_next; Trusted_Connection = True; "
);
using (var context = new TestDbContext(optionsBuilder.Options))
{
var command = fixture.Create<PostRegisterStudentCommand>();
var handler = new PostRegisterStudentCommandHandler(context);
try
{
handler.Handle(command);
}
catch (DbUpdateException e)
{
var sb = new StringBuilder();
sb.AppendLine($"DbUpdateException error details - {e?.InnerException?.InnerException?.Message}");
foreach (var eve in e.Entries)
{
sb.AppendLine($"Entity of type {eve.Entity.GetType().Name} in state {eve.State} could not be updated");
}
sb.ShouldBeNull();
}
var dbStudent = context.Students.FirstOrDefault();
dbStudent.ShouldNotBeNull();
dbStudent.User.FirstName.ShouldBe(command.FirstName);
}
}
I got the exception catch part from an EF 6 stackoverflow answer.
I've search everything I can think of to find a example of extracting entity validation issues (DbEntityValidationException from EF6) in EF Core but cannot find anything that seems to work.
As a suggestion from this EF Core github issue, I attempted to do some annotation validation like this. But this didn't find the issues that the db was having with my student object.
Indeed EF7 lacks the validation that it is available in EF6. This seems to be a design choice as validation is assumed before models are sent to be saved (and also DB constraints might be used as a safety net).
This article shows how to manually perform validations before saving data. However, beware that this works for data annotation only. My slightly changed version is the following:
public void Validate()
{
var entities = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
.Select(e => e.Entity);
foreach (var entity in entities)
{
var validationContext = new ValidationContext(entity);
Validator.ValidateObject(entity, validationContext, validateAllProperties: true);
}
}
My advice is to have a method to handle both type of exceptions (validation and database), something like the following:
try
{
Validate();
Context.SaveChanges();
}
catch(ValidationException exc)
{
Logger.LogError(exc, $"{nameof(SaveChanges)} validation exception: {exc?.Message}");
return false;
}
catch (DbUpdateException exc)
{
Logger.LogError(exc, $"{nameof(SaveChanges)} db update error: {exc?.InnerException?.Message}");
return false;
}

Can't update with .Attach()

I'm trying to update my UserRoles table but it won't update. I'm trying to update two things : 1. the email 2. the user role. Because the update needs to happen in 2 tables I'm using two separate commands. When I run the update on the Email alone (Users) it works but if I update the role (AspUserRoles) it does nothing. When I run it both it doesn't work either because UserRoles.Attach(userRole) is preventing it from updating. I also get no errors.
I checked if ApplicationRole.Id and ApplicationUser.Id has a value and it does return the value I want.
Here's my UserController.cs :
public async Task<IActionResult> Edit(UserViewModel model, Guid id)
{
var alert = new Alert();
try
{
if(!ModelState.IsValid)
{
alert.Message = alert.ExceptionMessage = ApplicationDbContextMessage.INVALID;
throw new Exception();
}
var originalModel = ApplicationDbContext.Users.FirstOrDefault(u => u.Id == id);
var userRole = ApplicationDbContext.UserRoles.FirstOrDefault(i => i.UserId == id);
if(originalModel == null)
{
alert.Message = alert.ExceptionMessage = ApplicationDbContextMessage.NOTEXISTS;
throw new Exception();
}
originalModel.Email = model.ApplicationUser.Email;
userRole.RoleId = model.ApplicationRole.Id;
ApplicationDbContext.Users.Attach(originalModel);
ApplicationDbContext.UserRoles.Attach(userRole);
ApplicationDbContext.Entry(originalModel).State = EntityState.Modified;
if (await ApplicationDbContext.SaveChangesAsync() == 0)
{
alert.Message = alert.ExceptionMessage = ApplicationDbContextMessage.EDITNOK;
throw new Exception();
}
alert.Message = ApplicationDbContextMessage.EDITOK;
return RedirectToAction("Index");
}
catch(Exception ex)
{
alert.Type = AlertType.Error;
alert.ExceptionMessage = ex.Message;
model = await ViewModel(model.ApplicationUser);
ModelState.AddModelError(string.Empty, alert.ExceptionMessage);
}
return View(model);
}
The way you are modifying data in this code, you don't need to call Attach or Add on the Context to let it know about changes to entities, that will happen automatically.
From the moment you pull an entity out of a DbSet of the DbContext it is being tracked (attached) by that DbContext. When you call SaveChanges on the DbContext it will scan any entities that it is tracking, comparing current values to old values, to find changes. Those changes then get sent to the data base.
You should literally be able to remove 3 lines of code from what you originally posted and have it work.
...
originalModel.Email = model.ApplicationUser.Email;
userRole.RoleId = model.ApplicationRole.Id;
ApplicationDbContext.Users.Attach(originalModel); // <--- Delete this line
ApplicationDbContext.UserRoles.Attach(userRole); // <--- Delete this line
ApplicationDbContext.Entry(originalModel).State = EntityState.Modified; // <--- Delete this line
if (await ApplicationDbContext.SaveChangesAsync() == 0)
...
A little something else I noticed. It looks like you might be using one single DbContext instance for the entire application. That is usually considered an "Anti-Patern" in Entity Framework. You should create a new DbContext instance (with using) for every "logical" operation you perform. That instance should only be alive for the life of that operation.
In MVC, this is usually one DbContext instance per ActionMethod.

Handle Concurrency with Transaction in Entity Framework while saving collection

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

Entity Framework entity state is modified without any modification

I have the following issue on update of the entities. Given below is my WCF method. (Update is called by public Save method after determining if it is update or add)
protected bool UpdateSalesMaster(SalesMaster order)
{
using (var context = new MyContext())
{
SalesMaster original = context.SalesMasters.FirstOrDefault(o => o.OrderID == order.OrderID);
if (original != null)
{
context.Entry(original).CurrentValues.SetValues(order);
foreach (SalesDetail detail in order.SalesDetails)
{
if (detail.OrderDetailID == 0)
context.SalesDetails.Add(detail);
else
{
SalesDetails originalDetail = context.SalesDetails.FirstOrDefault(o => o.OrderDetailID == detail.OrderDetailID);
if (originalDetail != null)
context.Entry(originalDetail).CurrentValues.SetValues(detail);
}
}
context.SaveChanges();
return true;
}
else
{
throw new FaultException(string.Format("Invalid Order specified: {0}", order.OrderID));
}
}
}
When I just update the OrderDate in SalesMaster and don't change any in the detail, an update query is fired to the database for detail. I expected to see Update query only for SalesMaster.
Can someone let me know what am I doing wrong here? I don't want to fire update queries to the DB if nothing is changed.
I use the approach of getting the original value from the database to determine if any values are updated using context.Entry(originalDetail).CurrentValues.SetValues(detail);
I also override the SaveChanges to set the LastModified date by checking for IAuditable implementation of the entity. This is when I find that the state of the detail entity is identified as modified. But the only update that happens in the DB is LastModifiedBy which was updated in my save changes. I am not sure how it was set to state of Modified when nothing changed in detail.
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditable>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
if (entry.State == EntityState.Added)
entry.Entity.DateCreated = DateTime.Now;
if (entry.State == EntityState.Modified)
{
entry.Property(a => a.CreatedByUser).IsModified = false;
entry.Property(a => a.DateCreated).IsModified = false;
}
entry.Entity.DateModified = DateTime.Now;
}
}
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
throw ex;
}
}
My solution structure is:
Client - Windows forms UI
Entities - POCO as seperate library
WCF - All business logic, add, update, delete of objects.
Data - Entity Framework context with Fluent mapping.
Depending on how you set up you POCOs, EF will default to one of two ways to check changes on an entity.
If ALL your POCOs properties are virtual, EF will use proxy object that inherit your POCO type, with all the properties overridden to track changes.
I assume that in this circumstance, using SetValues on the whole object WILL trigger the dirty flag, that will cause EF to generate an Update query to the database.
If your are not using proxies, your IAuditable implementation would be the primary suspect as brumScouse suggested.

How to update a database with new information

I am terrible with databases so please bear with me.
I have a program that gets some user information and adds it to a database table. I then later need to get more information for that table, and update it. To do so I have tried doing this:
public static void updateInfo(string ID, string email, bool pub)
{
try
{
//Get new data context
MyDataDataContext db = GetNewDataContext(); //Creates a new data context
//Table used to get user information
User user = db.Users.SingleOrDefault(x => x.UserId == long.Parse(ID));
//Checks to see if we have a match
if (user != null)
{
//Add values
user.Email = email;
user.Publish = publish;
}
//Prep to submit changes
db.Users.InsertOnSubmit(user);
//Submit changes
db.SubmitChanges();
}
catch (Exception ex)
{
//Log error
Log(ex.ToString());
}
}
But I get this error:
System.InvalidOperationException: Cannot add an entity that already exists.
I know this is because I already have an entry in the table, but I don't know how to edit the code to update, and not try to make a new one?
Why does this not work? Wouldn't submitting changes on a current item update that item and not make a new one?
The problem is
//Prep to submit changes
db.Users.InsertOnSubmit(user);
Because you got the user from the DB already, you don't need to re-associate it with the context.
Comment that out and you're good to go.
Just a style / usage comment as well. You should dispose your context:
public static void updateInfo(string ID, string email, bool pub)
{
try
{
using (MyDataDataContext db = GetNewDataContext())
{
User user = db.Users.SingleOrDefault(x => x.UserId == long.Parse(ID));
if (user != null)
{
user.Email = email;
user.Publish = publish;
}
db.SubmitChanges();
}
}
catch (Exception ex)
{
//Log error
Log(ex.ToString());
// TODO: Consider adding throw or telling the user of the error.
// throw;
}
}

Categories

Resources