optimisticconcurrencyexception was caught when updating entity relation using stub entities - c#

This code is giving me optimisticconcurrencyexception was caught. I am simply using a Stub Entity to get an existing record and trying to update a couple values. I am not sure how to resolve the exception. Any help is very much appreciated:
using (MiscEntities ctx = new MiscEntities())
{
var m = ctx.Rates.FirstOrDefault(m => m.UserId == UserIdGuid);
DataAccess.Rate oldDbRate = new DataAccess.Rate { RatingId = m.RatingId };
ctx.AttachTo("Rates", dbRate);
dbRate.Rating = Rating;
dbRate.DateLastModified = DateTime.Now;
ctx.SaveChanges();
}

EF, by default, uses an optimistic concurrency model, meaning that locks are not held on data in the source between when the data is queried and when it is updated. So it doesn't check for any conflicts before saving changes to the database. With any conflicts an OptimisticConcurrencyException is raised (for more information check out How to: Manage Data Concurrency in the Object Context).
It's good practice (when you make updates in a high concurrency scenario) to call Refresh quite often. In this case try using a RefreshMode of ClientWins to to refresh the values in the client store before sending them to the database, like this:
using (MiscEntities ctx = new MiscEntities())
{
try
{
var m = ctx.Rates.FirstOrDefault(m => m.UserId == UserIdGuid);
DataAccess.Rate oldDbRate = new DataAccess.Rate { RatingId = m.RatingId };
ctx.AttachTo("Rates", dbRate);
dbRate.Rating = Rating;
dbRate.DateLastModified = DateTime.Now;
ctx.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
ctx.Refresh(RefreshMode.ClientWins, dbRate);
ctx.SaveChanges();
}
}
EDIT: After more reading, and re-reading that error message it makes sense, you cannot attach an object to an ObjectContext is that object has already that has already been cached by the ObjectStateManager.
The solution is real simple, attach your objects before doing any operations/query in your ObjectContext. This allows you to prevent any double-tracking requests. If the ObjectContext needs your Entity later, it will retrieve the instance you attached before and you're good to go. Take a look at this code and see if it helps (Sorry don't have Visual Studio 2010 opened right now)
using (MiscEntities ctx = new MiscEntities())
{
try
{
ctx.AttachTo("Rates", dbRates);
var m = ctx.Rates.FirstOrDefault(m => m.UserId == UserIdGuid);
DataAccess.Rate oldDbRate = new DataAccess.Rate { RatingId = m.RatingId };
dbRate.Rating = Rating;
dbRate.DateLastModified = DateTime.Now;
ctx.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
ctx.Refresh(RefreshMode.ClientWins, dbRate);
ctx.SaveChanges();
}
}

Related

How to bulk insert/update lots of data in the correct way?

Following thing boggles my mind:
I have to bulk insert a lot of changes, some are inserts some are updates. I am not sure how to do it the best way.
Logic looks something like this:
public class Worker
{
public void Run(){
var mailer = new Mailer();
HashSet<DbElements> dbElementsLookUp = new HashSet<DbElement>(dbContext.DbElements);
List<Element> elements = GetSomeChangesFromSomewhere();
var dbElementsToSave = new List<DbElements>();
foreach(var element in elements)
{
CreateOrUpdateDbElement(element, dbElementsToSave);
// Sends some data based on the element - due to legacy implementation it uses its own context
mailer.SendSomeLogging(element);
}
try
{
dbContext.ChangeTracker.DetectChanges();
dbContext.Set<DbElement>().AddRange(dbElementsToSave);
dbContext.SaveChanges();
}
catch (Exception e)
{
LogErrors(e);
}
}
private CreateOrUpdateDbElement(ElementDto element, HashSet<DbElement> lookUp, List<DbElement> dbElementsToSave)
{
var entity = lookUp.FirstOrDefault(e => e.Id == element.Id);
if(element is not null)
{
entity.SomeProperty = element.SomeProperty;
dbContext.Configuration.AutoDetectChangesEnabled = false;
dbContext.Entry(entity).State = EntityState.Modified;
dbContext.Configuration.AutoDetectChangesEnabled = true;
}
else
{
dbElementsToSave.Add(new DbElement
{
SomeProperty = element.SomeProperty,
CreationDate = DateTime.Now
});
}
}
}
I'm not sure what's the best way to do this, especially for the DetectChanges. Is it save to disable the autodetectchanges and call the detectchanges outside of the foreach. I am working with a lot of data and due to the legacy implementation it is pretty slow because for each mail there is a write operation on the database. It actually works on another instance of the context so it does not interfer with the saving of the objects of dbelements.
Is it better to add the entities to update to another list and do the same as for the adding of entities?

Marten Not Storing Entity

I'm working with Marten as my data layer and it's been great so far, but I've run into an issue that just doesn't make sense. I have a simple method that saves a transaction (a purchase) then updates a listing, adding the ID of the transaction to a collection. My problem is, it appears that Marten is not storing my updated listing, although it is storing the transaction.
When I look in the database, the TransactionIds property is null, but if I step through the code, everything seems to execute correctly. Am I doing something wrong here?
public async Task CreateListingTransactionAsync(ListingTransaction transaction)
{
if (transaction == null)
throw new ValidationException("Transaction is required to create a transaction");
bool isNew = transaction.Id == Guid.Empty;
await _listingTransactionValidator.ValidateAndThrowAsync(transaction);
using (var session = _store.LightweightSession())
{
session.Store(transaction);
if (isNew)
{
var listing = await session.LoadAsync<Listing>(transaction.ListingId);
if (listing == null)
throw new EntityNotFoundException($"Listing with Id: {transaction.ListingId} not found");
if (listing.TransactionIds == null)
listing.TransactionIds = new List<Guid>();
listing.TransactionIds.Add(transaction.Id);
session.Store(listing);
}
await session.SaveChangesAsync();
}
}
There could be a problem with the serialization of TransactionIds collection.
If that's not the case then here are some random things to try (and try to understand why it worked later):
Try session.Update(listing); instead of session.Store(listing);.
Try different type of document session. http://jasperfx.github.io/marten/documentation/troubleshoot/

Db context - multiple usages

so I stumbled upon a weird thing just now.
I have a test which changes the until date of a workingsite to yesterday like so:
using (var db = new ApplicationDbContext())
{
var empFromDb = db.Employees.Include(x => x.WorkingSites).First(x => x.Id == _employeeId);
empFromDb.WorkingSites.Add(workingSiteToEnd);
db.SaveChanges();
//Act
ServiceFactory.CreateEmployeeService().EndWorkingSitePeriod(workingSiteToEnd);
//Assert
var workingSiteFromDb = db.WorkingSites.First(x => x.Id == workingSiteToEnd.Id);
Assert.AreEqual(DateTime.Now.AddDays(-1).Date, workingSiteFromDb.WorksUntil.Date);
}
In this test my assert fails and the until hasn't changed for the object workingSiteFromDb, yet in my code I do change it and save changes to the database.
note: my database did update! I checked inside the database and the date is altered correctly.
Now I didn't know what was going on and so I stopped the using right after the first savechanges and opened it again right before I call workingSiteFromDb.
If I do this, it works.
Note that I use another using within the EndWorkingSitePeriod method.
How come my database does update but the object only updates when I use a second using?
This is the EndWorkingSitePeriod method:
public void EndWorkingSitePeriod(int workingSiteId)
{
using (var db = new ApplicationDbContext())
{
var workingSiteFromDb = db.WorkingSites.Include(x => x.Employee).First(x => x.Id == workingSiteId);
workingSiteFromDb.EndPeriod();
db.SaveChanges();
}
}
The workingSite.EndPeriod just sets the UntilDateto DateTime.Now.AddDays(-1)
First, you're obtaining workingSiteToEnd somewhere and adding it to context, created in 1st line of your sample. Then, you're saving the changes (workingSiteToEnd now in the database and in the context).
Then, you're creating the second context in EndWorkingSitePeriod method. Using that context, you're obtaining new workingSiteFromDb instance (it doesn't relate to workingSiteToEnd from above). You're modifying it, and saving changes.
Now, you're trying to test the changes you've made, but original workingSiteToEnd is still present in context. This means, that when you'll try to load it from database again, context, during materialization process, will look up for entity with the same key in its local cache, will find it, and will return existing entity, which is original, unchanged workingSiteToEnd (you can compare references, they'll be equal).
When you're closing using block right after first SaveChanges, and then creating new one, you're creating new context, which will load new instance for workingSiteFromDb, and the test will pass.
Do not nest usings of the same DbContext. Instead, if you need to use the same context in called method, pass it with parameter, e.g.:
using (var db = new ApplicationDbContext())
{
var empFromDb = db.Employees.Include(x => x.WorkingSites).First(x => x.Id == _employeeId);
empFromDb.WorkingSites.Add(workingSiteToEnd);
db.SaveChanges();
//Act
ServiceFactory.CreateEmployeeService().EndWorkingSitePeriod(workingSiteToEnd, db);
//Assert
var workingSiteFromDb = db.WorkingSites.First(x => x.Id == workingSiteToEnd.Id);
Assert.AreEqual(DateTime.Now.AddDays(-1).Date, workingSiteFromDb.WorksUntil.Date);
}
private void EndWorkingSitePeriod(int workingSiteId, ApplicationDbContext db)
{
var workingSiteFromDb = db.WorkingSites.Include(x => x.Employee).First(x => x.Id == workingSiteId);
workingSiteFromDb.EndPeriod();
db.SaveChanges();
}
// if you need it public, then use this too
public void EndWorkingSitePeriod(int workingSiteId)
{
using (var db = new ApplicationDbContext())
{
EndWorkingSitePeriod(workingSiteId, db);
}
}

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

Race condition makes nHibernate to create duplicate entry

I have a race condition on nHibernate that's creating duplicate entries on my database. Unfortunately, I cannot create an UNIQUE index on database, thus I would like to solve this error using only nHibernate methods. It is a web application that might run on a web farm (hence I guess a system lock should not solve the problem neither). The simplified situation follows:
var current = UnitOfWorkManager.Instance.Current;
current.BeginTransaction(IsolationLevel.Serializable);
try {
var myEntity = MyFactory.MyEntityRepository.GetBy(product, company);
// race condition happens between the previous statement and Save() method.
if (myEntity == null)
{
myEntity = new MyEntity();
myEntity.Product = product;
myEntity.Company = company;
myEntity.Date = date;
myEntity.CurrentUser = currentUser;
myEntity.IsManual = true;
myEntity.Save();
}
else
{
myEntity.IsManual = false;
myEntity.Save();
}
current.CommitTransaction();
}
catch {
current.RollbackTransaction();
throw;
}
I am new to nHibernate so maybe I am missing some basics here. I'd appreciate any feedback. :)
After reading the nHibernate Manual, I think your problem is maybe your second call to save if subProjectToSupplier isn't null. Because the nHibernate manual says "save" does an insert.
Try SaveOrUpdate
You should wrap your Save() in a transaction, ideally you could implement a pattern such as IUnitOfWork or use the SessionFactory, for example:
using (var transaction = session.BeginTransaction()) {
myEntity = new MyEntity();
myEntity.Product = product;
myEntity.Company = company;
myEntity.Date = date;
myEntity.CurrentUser = currentUser;
myEntity.IsManual = true;
myEntity.Save();
transaction.Commit();
}
OK, so you are asking for NHibernate solution here. I am not sure if this can solve in 100% your problem but try this one.
Use ReadCommitted isolation level.
Implement an interceptor for your needs http://nhibernate.info/doc/nh/en/#manipulatingdata-interceptors
Within the interceptor you can do:
Query the DB if there is a record on your unique column. Then you can apply some sort of a merge mechanism
Check the current session if there are other objects like yours pending to be persisted.
================
Other solution is to keep shared cache of between the farm (memcache) and track objects you do not want to be duplicated.
PS: I can talk more about each point. Just tell me if something sounds like a solution for you

Categories

Resources