структура сущности - это делает грязное чтение? - PullRequest
1 голос
/ 06 сентября 2011

У меня есть немного кода linq для сущностей в веб-приложении.Это в основном ведет учет того, сколько раз приложение было загружено.Я беспокоюсь, что это может произойти:

  • Сессия 1 читает счетчик загрузок (например, 50)
  • Сессия 2 читает счетчик загрузок (снова 50)
  • Сессия 1 увеличивает его и записывает в БД (хранилища базы данных 51)
  • Сессия 2 увеличивает его и записывает в БД (хранилища базы 51)

Это мойкод:

private void IncreaseHitCountDB()
{
    JTF.JTFContainer jtfdb = new JTF.JTFContainer();

    var app =
        (from a in jtfdb.Apps
         where a.Name.Equals(this.Title)
         select a).FirstOrDefault();

    if (app == null)
    {
        app = new JTF.App();
        app.Name = this.Title;
        app.DownloadCount = 1;

        jtfdb.AddToApps(app);
    }
    else
    {
        app.DownloadCount = app.DownloadCount + 1;
    }

    jtfdb.SaveChanges();
}

Возможно ли, что это могло произойти?Как я мог предотвратить это?

Спасибо, Фидель

Ответы [ 3 ]

2 голосов
/ 11 мая 2012

Entity Framework по умолчанию использует оптимистическую модель параллелизма. Google говорит, что оптимизм означает « с надеждой и уверенностью в будущем », и именно так действует Entity Framework. То есть, когда вы звоните SaveChanges(), это означает " с надеждой и уверенностью ", что проблема параллелизма не возникнет, поэтому он просто пытается сохранить ваши изменения.

Другая модель, которую может использовать Entity Framework, должна называться пессимистической моделью параллелизма (" с ожиданием худшего возможного результата "). Вы можете включить этот режим для каждого объекта. В вашем случае вы бы включили его на объекте App . Вот что я делаю:

Шаг 1. Включение проверки параллельности для сущности

  1. Щелкните правой кнопкой мыши файл .edmx и выберите Открыть с помощью ...
  2. Выберите XML (Text) Editor во всплывающем диалоговом окне и нажмите OK.
  3. Найдите сущность приложения в ConceptualModels . Я предлагаю переключать контуры и просто расширять теги по мере необходимости. Вы ищете что-то вроде этого:

    <edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
      <!-- EF Runtime content -->
      <edmx:Runtime>
        <!-- SSDL content -->
        ...
        <!-- CSDL content -->
        <edmx:ConceptualModels>
          <Schema Namespace="YourModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
            <EntityType Name="App">
    
  4. Под EntityType вы должны увидеть кучу <Property> тегов. Если существует с Name="Status", измените его, добавив ConcurrencyMode="Fixed". Если свойство не существует, скопируйте его в:

    <Property Name="Status" Type="Byte" Nullable="false" ConcurrencyMode="Fixed" />
    
  5. Сохраните файл и дважды щелкните файл .edmx , чтобы вернуться к представлению конструктора.

Шаг 2. Обработка параллелизма при вызове SaveChanges()

SaveChanges() выдаст одно из двух исключений. Знакомая UpdateException или OptimisticConcurrencyException .

Если вы внесли изменения в Entity, для которого установлено значение ConcurrencyMode="Fixed", Entity Framework сначала проверит хранилище данных на наличие изменений, внесенных в него. Если есть изменения, будет выброшено OptimisticConcurrencyException. Если не было внесено никаких изменений, оно продолжится в обычном режиме.

Когда вы ловите OptimisticConcurrencyException, вам нужно вызвать Refresh () метод вашего ObjectContext и повторить ваш расчет, прежде чем пытаться снова. Вызов Refresh() обновляет сущность (и), а RefreshMode.StoreWins означает, что конфликты будут разрешаться с использованием данных в хранилище данных. Одновременное изменение DownloadCount является конфликтом.

Вот как я бы сделал ваш код похожим. Обратите внимание, что это более полезно, когда у вас много операций между получением вашей сущности и вызовом SaveChanges().

    private void IncreaseHitCountDB()
    {
        JTF.JTFContainer jtfdb = new JTF.JTFContainer();

        var app =
            (from a in jtfdb.Apps
             where a.Name.Equals(this.Title)
             select a).FirstOrDefault();

        if (app == null)
        {
            app = new JTF.App();
            app.Name = this.Title;
            app.DownloadCount = 1;

            jtfdb.AddToApps(app);
        }
        else
        {
            app.DownloadCount = app.DownloadCount + 1;
        }

        try
        {
            try
            {
                jtfdb.SaveChanges();
            }
            catch (OptimisticConcurrencyException)
            {
                jtfdb.Refresh(RefreshMode.StoreWins, app);
                app.DownloadCount = app.DownloadCount + 1;
                jtfdb.SaveChanges();
            }
        }
        catch (UpdateException uex)
        {
            // Something else went wrong...
        }
    }
1 голос
/ 06 сентября 2011

Вы можете предотвратить это, если вы только запросите столбец количества загрузок непосредственно перед тем, как собираетесь увеличивать его, чем дольше будет время между чтением и приращением, тем дольше будет читать его другой сеанс (и позднее переписывать - неправильно - увеличенный номер) и, таким образом, испортить счет.

с одним запросом SQL:

UPDATE Data SET Counter = (Counter+1)

, так как его Linq To Entities, это означает отложенное выполнение, для другого сеанса, чтобы испортить счетчик (увеличить ту же базу, потеряв там 1 счетчик), он должен будет попытаться увеличить приложение. Количество загрузок, которое я считаю между двумя линии:

    else
    {
        app.DownloadCount += 1; //First line
    }

    jtfdb.SaveChanges();  //Second line
}

это означает, что окно для изменения, в результате чего предыдущий счет является старым, настолько мало, что для такого приложения практически невозможно.

Поскольку я не являюсь LINQ pro, я не знаю, получает ли LINQ действительно app.DownLoadCount, прежде чем добавить его или просто добавляет один с помощью какой-либо команды SQL, но в любом случае вам не нужно беспокоиться об этом imho

0 голосов
/ 06 сентября 2011

Вы можете легко проверить , что произойдет в этом сценарии - запустить поток, перевести его в спящий режим, а затем запустить другой.

else
{
    app.DownloadCount = app.DownloadCount + 1;
}

System.Threading.Thread.Sleep(10000);
jtfdb.SaveChanges();

Но простой ответ заключается в том, что нет, Entity Framework не выполняет проверку параллелизма по умолчанию (MSDN - сохранение изменений и управление параллелизмом) .

Этот сайт предоставит вам некоторую информацию.

Ваши варианты

  • для включения проверки параллелизма, что будет означать, что если два пользователя загружают одновременно и первые обновления после чтения второго, но до обновления второго, вы получите исключение.
  • создать хранимую процедуру, которая будет увеличивать значение в таблице напрямую, и вызывать хранимую процедуру из кода за одну операцию - например, IncrementDownloadCounter. Это обеспечит отсутствие «чтения» и, следовательно, невозможности «грязного чтения».
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...