для простоты написал псевдокодом. но идея в том что табла меняется из 2 мест по сути в рамках энтити контекста и в скл самом и кидается такой экспешн DbUpdateConcurrencyException
begin tran
efContext.ExecuteSqlRaw("update users set time = getdate() where id = 5")
var user = efContext.Single(a => a.id == 5);
user.time = Now
efContex.Users.Update(user);
efContext.SaveChanges()
commit tran
решение я нашел тут
но мне показалось странным выставлять значение в обработке эксепшна.
Есть решение получше или всё же это нормально?
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);
}
}
}
Имхо, этот вариант борьбы с concurrency изначально порочен.
Откуда тебе знать, что такая подмена значений не поломает бизнес-логику в целом?
Например, если на основе "старых" значений из конфликтующей сущности были сделаны изменения в других таблицах или вообще в сторонних системах.
Как быть уверенным, что поменяв значения в cath() {...}, ты снова не отхватишь concurrency exception, который уже не перехватить?
А касательно правильного подхода, it depends.
1. Если это фоновый обработчик. Типа раз в минуту взять данные из базы, пошуршать, положить обновленные данные в базу. Тогда проще сделать ретрай всей бизнес-транзакции: прочитать-изменить-сохранить.
2. Если это сохраняется из UI, то можно
2.1 Действовать как в п.1, т.е. считаем что last write wins и делаем цикл прочитать-применить изменения-обновить до тех пор, пока не пройдет без ConcurrencyException.
2.2 Показать юзеру ошибку: "Версия данных в БД отличается от текущей. Перезагрузите страницу." Реализация сведется к простому перехвату exception и показу сообщения.
2.3 Показать юзеру ошибку: "Версия данных в БД отличается от текущей, обновить из БД?" Это сведется к перехвату exception, вытаскиванию актуальных БД-значений и запихиванию их в поля на форме редактирования, если пользователь выбрал "Обновить из БД".
Здравствуйте, RushDevion, Вы писали:
M>>Есть решение получше или всё же это нормально?
RD>Имхо, этот вариант борьбы с concurrency изначально порочен. RD>Откуда тебе знать, что такая подмена значений не поломает бизнес-логику в целом? RD>Например, если на основе "старых" значений из конфликтующей сущности были сделаны изменения в других таблицах или вообще в сторонних системах. RD>Как быть уверенным, что поменяв значения в cath() {...}, ты снова не отхватишь concurrency exception, который уже не перехватить?
RD>А касательно правильного подхода, it depends. RD>1. Если это фоновый обработчик. Типа раз в минуту взять данные из базы, пошуршать, положить обновленные данные в базу. Тогда проще сделать ретрай всей бизнес-транзакции: прочитать-изменить-сохранить. RD>2. Если это сохраняется из UI, то можно RD>2.1 Показать юзеру ошибку: "Версия данных в БД отличается от текущей. Перезагрузите страницу." Реализация сведется к простому перехвату exception и показу сообщения. RD>2.2 Показать юзеру ошибку: "Версия данных в БД отличается от текущей, обновить из БД?" Это сведется к перехвату exception, вытаскиванию актуальных БД-значений и запихиванию их в поля на форме редактирования, если пользователь выбрал "Обновить из БД".
тут работа из юая идет, просто 2 шага и первый из-за большого кол-ва скл кода сделали в виде процедуры которая в одном месте апдет таблы делает, которую потом же апдейтит энтити обновленным значением.
то есть юзер тут ничего не изменит.
тут вариант видимо перенос логики из энтити в даппер какой-то который не имеет оптимистичных блокировок либо как прочитал тут
Здравствуйте, merge, Вы писали: M>тут вариант видимо перенос логики из энтити в даппер какой-то который не имеет оптимистичных блокировок либо как прочитал тут
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, merge, Вы писали: M>>тут вариант видимо перенос логики из энтити в даппер какой-то который не имеет оптимистичных блокировок либо как прочитал тут
Здравствуйте, merge, Вы писали:
M>Столкнулся тут с ошибкой в таком коде на шарпе
M>для простоты написал псевдокодом. но идея в том что табла меняется из 2 мест по сути в рамках энтити контекста и в скл самом и кидается такой экспешн DbUpdateConcurrencyException
M>
M>begin tran
M> efContext.ExecuteSqlRaw("update users set time = getdate() where id = 5")
M> var user = efContext.Single(a => a.id == 5);
M> user.time = Now
M> efContex.Users.Update(user);
M> efContext.SaveChanges()
M>commit tran
M>
M>решение я нашел тут M>но мне показалось странным выставлять значение в обработке эксепшна. M>Есть решение получше или всё же это нормально?
M>
M> catch (DbUpdateConcurrencyException ex)
M> {
M> foreach (var entry in ex.Entries)
M> {
M> if (entry.Entity is Person)
M> {
M> var proposedValues = entry.CurrentValues;
M> var databaseValues = entry.GetDatabaseValues();
M> foreach (var property in proposedValues.Properties)
M> {
M> var proposedValue = proposedValues[property];
M> var databaseValue = databaseValues[property];
M> // TODO: decide which value should be written to database
M> // proposedValues[property] = <value to be saved>;
M> }
M> // Refresh original values to bypass next concurrency check
M> entry.OriginalValues.SetValues(databaseValues);
M> }
M> else
M> {
M> throw new NotSupportedException(
M> "Don't know how to handle concurrency conflicts for "
M> + entry.Metadata.Name);
M> }
M> }
M> }
M>
Сущность можно обновлять двумя способами. Первый:
using (var db=new Db())
{
var m = db.Managers.Find(1);
m.Name = "MyName_1";
db.SaveChanges();
}
EF генерит такие запросы:
info: 16.06.2022 09:20:43.803 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (51ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [m].[Id], [m].[Family], [m].[Name]
FROM [Managers] AS [m]
WHERE [m].[Id] = @__p_0
info: 16.06.2022 09:20:44.006 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (24ms) [Parameters=[@p1='?' (DbType = Int32), @p0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [Managers] SET [Name] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
Ясное дело что после select сущность в бд могла быть изменена другим запросом.
Второй способ:
using (var db = new Db())
{
var m = new Manager() { Id = 1, Name = "MyName_2", Family = "Pupkin" };
db.Attach<Manager>(m).State = EntityState.Modified;
db.SaveChanges();
}
Здравствуйте, merge, Вы писали: M>а он жив и его поддерживают?
А то! M>ну перенос это дело небыстрое в любом случае, а в одном месте использовать это плохо
Верно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, merge, Вы писали:
M>решение я нашел тут M>но мне показалось странным выставлять значение в обработке эксепшна. M>Есть решение получше или всё же это нормально?
Это т.н. optimistic concurrency. Только ты зря цикл не процитировал в примере, он важен (кому интересно — вот статья откуда пример).
Algorithms built around CAS typically read some key memory location and remember the old value. Based on that old value, they compute some new value. Then they try to swap in the new value using CAS, where the comparison checks for the location still being equal to the old value. If CAS indicates that the attempt has failed, it has to be repeated from the beginning: the location is re-read, a new value is re-computed and the CAS is tried again.
Здравствуйте, Sinclair, Вы писали:
M>>тут вариант видимо перенос логики из энтити в даппер какой-то который не имеет оптимистичных блокировок либо как прочитал тут
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здравствуйте, merge, Вы писали:
M>>решение я нашел тут M>>но мне показалось странным выставлять значение в обработке эксепшна. M>>Есть решение получше или всё же это нормально?
НС>Это т.н. optimistic concurrency. Только ты зря цикл не процитировал в примере, он важен (кому интересно — вот статья откуда пример). НС>
НС>Algorithms built around CAS typically read some key memory location and remember the old value. Based on that old value, they compute some new value. Then they try to swap in the new value using CAS, where the comparison checks for the location still being equal to the old value. If CAS indicates that the attempt has failed, it has to be repeated from the beginning: the location is re-read, a new value is re-computed and the CAS is tried again.
самое интересное что у меня этот код свалился в такую же ошибку.
так вот сделал
if (entry.Entity is Promo)
{
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] = proposedValue;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(proposedValues);
}