lock объекта по Id

203
23 октября 2017, 22:57

Столкнулся со следующей проблемой. Имеется некий метод,записывающий сущность в бд, примерно такой:

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. Как это сделать? Может быть у кого-то есть идеи на этот счёт?

Answer 1

А как вам варинт попробовать это выполнить через транзакции?

Это избавит вас от необходимости писать 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);
        }
    }
}
READ ALSO
Как хранить заготовленный C# Dictionary?

Как хранить заготовленный C# Dictionary?

У меня есть около десяти тысяч строчек в таблице следующего формата:

238
Поиск по объектам JavaScript

Поиск по объектам JavaScript

Здравствуйте, помогите пожалуйста решить мою задачуУ меня есть три таблицы в базе данных: Материк, Страна, Город

399
Раскрывающийся pie chart d3.js

Раскрывающийся pie chart d3.js

Раскрывать элемент диаграммы по которому кликнулиНа входе такой массив, где child - это это те элементы, которые надо раскрыть

317
Поддержка подсветки и синтаксиса JSX

Поддержка подсветки и синтаксиса JSX

Изучаю библиотеку React и использую Brackets

226