I am just starting to learn about competitiveness processing in ef to make consistent applications and I have some questions, Microsoft documentation says the following:
using var context = new PersonContext();
// Fetch a person from database and change phone number
var person = context.People.Single(p => p.PersonId == 1);
person.PhoneNumber = "555-555-5555";
// Change the person's name in the database to simulate a concurrency conflict
context.Database.ExecuteSqlRaw(
"UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Person)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
I have code of WeatherSerivce:
public WeatherForecast UpdateMeasurementsForToday()
{
var saved = false;
var date = DateTime.Today;
var specified = DateTime.SpecifyKind(date, DateTimeKind.Utc);
while (!saved)
{
try
{
var entityFromDb = _source.GetWeatherByDate(specified);
entityFromDb.TemperatureC = Random.Shared.Next(30);
_source.UpdateEntity(entityFromDb);
saved = true;
return entityFromDb;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is WeatherForecast)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
if (databaseValues is null)
throw new ArgumentException("Сущность была удалена");
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
throw new ArgumentException();
}
Code from _source repository:
public void UpdateEntity(WeatherForecast newEntity)
{
var entityFromDb = _ctx.Forecasts.First(c => c.Id.Equals(newEntity.Id));
entityFromDb.TemperatureC = newEntity.TemperatureC;
entityFromDb.SummaryUpdates++;
entityFromDb.Version = Guid.NewGuid();
_ctx.SaveChanges();
}
And I have a question, why when we get DbConcurrencyException we do some extra steps like in Miscrosoft documentation?
Can’t we just go back and ask for NEW data (I tried this way and it really didn’t work properly, the loop was endlessly throwing DbConcurrencyUpdateEx, I think it’s due to the fact that ef caches data).
Another issue is when we may need a situation where we need our proposed values and NEW values from the database to resolve a conflict, because our proposed values are based on the old ones, logically it seems as if we just need to write entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(databaseValues);
and kind of suggest new values
but in the documentation we again go through the properties in detail
Also probably a stupid question from the outside, but I genuinely don’t understand why my competitiveness test is unstable:
public async void UpdateWeather_Updatingsimultaneously_SummaryShouldConsiderUpdateCounters()
{
var rep1 = new EfWeatherRepository(_ctx1);
var rep2 = new EfWeatherRepository(efDbInitializer.Ctx);
var weatherService1 = new WeatherService(rep1, _fakeService.Object);
var weatherService2 = new WeatherService(rep2, _fakeService.Object);
var createdEntity = weatherService1.MeasureTodayWeather();
var task1 = Task.Run(() => weatherService1.UpdateMeasurementsForToday());
var task2 = Task.Run(() => weatherService2.UpdateMeasurementsForToday());
await Task.WhenAll(task1, task2);
var result = rep1.GetEntityById(createdEntity.Id);
result.SummaryUpdates.Should().Be(2);
}
I’m intercepting the conflict, updating the data and loading the weather entity into the database with the NEW data, but sometimes the test fails because the number of updates is 1 instead of 2.
Thank you for your time!
I used the code from the ms documentation