Столкнулся со следующей проблемой. Имеется некий метод,записывающий сущность в бд, примерно такой:
public void Update(Foo foo)
{
UpdateFirstPart(foo.First);
UpdateSecondPart(foo.Second);
UpdateThirdPart(foo.Third);
}
Foo выглядит примерно так:
class Foo
{
public int Id;
public Some1 First;
public Some2 Second;
public Some3 Third;
}
Смысл тут такой: чтобы корректно выполнить обновление нашей сущности, нужно сделать три шага: UpdateFirstPart, UpdateSecondPart и UpdateThirdPart. Проблема в том, что метод Update может выполняться из разных потоков одновременно. Если используются разные экземпляры foo, то проблем нет - две сущности параллельно обновляются и всё ок. Проблема возникает, если одновременно обновляются два Foo с одинаковыми Id В этом случае изменения в одном потоке могут перетирать изменения другого потока, притом в итоге сохранится часть изменений из первого потока и часть из второго, то есть в результате в бд записывается каша. Во избежание этого вполне логичным кажется поместить код в блокировку:
public void Update(Foo foo)
{
lock(_locker)
{
UpdateFirstPart(foo.First);
UpdateSecondPart(foo.Second);
UpdateThirdPart(foo.Third);
}
}
Всё работает. Но остаётся проблема, заключающаяся том, что эта блокировка работает всегда, в том числе и тогда, когда разные потоки обновляют разные экземпляры Foo (точнее Foo с разными Id)? а мне нужна блокировка тоьлко для тех случаев, когда из двух (или более) потоков происходит вызов Update для Foo с одинаковыми Id. Как это сделать? Может быть у кого-то есть идеи на этот счёт?
А как вам варинт попробовать это выполнить через транзакции?
Это избавит вас от необходимости писать lock-систему.
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Start a local transaction.
SqlTransaction sqlTran = connection.BeginTransaction();
// Enlist a command in the current transaction.
SqlCommand command = connection.CreateCommand();
command.Transaction = sqlTran;
try
{
// Execute two separate commands.
command.CommandText =
"INSERT INTO Production.ScrapReason(Name) VALUES('Wrong size')";
command.ExecuteNonQuery();
command.CommandText =
"INSERT INTO Production.ScrapReason(Name) VALUES('Wrong color')";
command.ExecuteNonQuery();
// Commit the transaction.
sqlTran.Commit();
Console.WriteLine("Both records were written to database.");
}
catch (Exception ex)
{
// Handle the exception if the transaction fails to commit.
Console.WriteLine(ex.Message);
try
{
// Attempt to roll back the transaction.
sqlTran.Rollback();
}
catch (Exception exRollback)
{
// Throws an InvalidOperationException if the connection
// is closed or the transaction has already been rolled
// back on the server.
Console.WriteLine(exRollback.Message);
}
}
}
Как развивать веб-проекты в 2026 году: технологии, контент E-E-A-T и факторы доверия
Современные инструменты для криптотрейдинга: как технологии помогают принимать решения
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники