Паралелност на база данни се отнася до ситуации, при които множество процеси или потребители имат достъп или променят едни и същи данни в база данни едновременно. Контролът на паралелността се отнася до специфични механизми, използвани за осигуряване на съгласуваност на данните при наличие на едновременни промени.

EF Core внедрява оптимистичен контрол на паралелността, което означава, че ще позволи на множество процеси или потребители да правят промени независимо без допълнителни разходи за синхронизиране или заключване. В идеалната ситуация тези промени няма да си пречат една на друга и следователно ще могат да успеят. В най-лошия случай два или повече процеса ще се опитат да направят противоречиви промени и само един от тях трябва да успее.

Как работи контролът на паралелността в EF Core

Свойствата, конфигурирани като токени за паралелност, се използват за прилагане на оптимистичен контрол на паралелността: всеки път, когато се извършва операция за актуализиране или изтриване по време на SaveChanges, стойността на токена за паралелност в базата данни се сравнява с оригиналната стойност, прочетена от EF Core.

  • Ако стойностите съвпадат, операцията може да завърши.
  • Ако стойностите не съвпадат, EF Core приема, че друг потребител е извършил конфликтна операция и прекъсва текущата транзакция.

Ситуацията, когато друг потребител е извършил операция, която е в конфликт с текущата операция, е известна като конфликт на едновременност.

Доставчиците на бази данни са отговорни за прилагането на сравнението на стойностите на токените за паралелност.

В релационни бази данни EF Core включва проверка за стойността на токена за паралелност в клаузата WHERE на всеки израз UPDATE или DELETE. След изпълнение на изразите, EF Core чете броя на редовете, които са били засегнати.

Ако няма засегнати редове, се открива конфликт на едновременност и EF Core хвърля DbUpdateConcurrencyException.

Например, може да искаме да конфигурираме LastName на Person да бъде токен за едновременност. След това всяка операция за актуализиране на Person ще включва проверката за едновременност в клаузата WHERE:

SQLCopy

UPDATE [Person] SET [FirstName] = @p1
WHERE [PersonId] = @p0 AND [LastName] = @p2;

Разрешаване на конфликти на едновременност

Продължавайки с предишния пример, ако един потребител се опита да запази някои промени в Person, но друг потребител вече е променил LastName, тогава ще бъде хвърлено изключение.

В този момент приложението може просто да информира потребителя, че актуализацията не е успешна поради противоречиви промени и да продължи. Но може да е желателно да подканите потребителя да се увери, че този запис все още представлява същото действително лице и да опитате отново операцията.

Този процес е пример за разрешаване на конфликт на едновременност.

Разрешаването на конфликт на паралелност включва обединяване на чакащите промени от текущия DbContext със стойностите в базата данни. Кои стойности се обединяват ще варират в зависимост от приложението и могат да бъдат насочени от потребителски вход.

Има три набора от стойности, които помагат за разрешаване на конфликт на паралелност:

  • Текущи стойности са стойностите, които приложението се е опитвало да запише в базата данни.
  • Оригиналните стойности са стойностите, които първоначално са били извлечени от базата данни, преди да бъдат направени каквито и да било редакции.
  • Стойностите на базата данни са стойностите, съхранявани в момента в базата данни.

Общият подход за справяне с конфликт на едновременност е:

  1. Хванете DbUpdateConcurrencyException по време на SaveChanges.
  2. Използвайте DbUpdateConcurrencyException.Entries, за да подготвите нов набор от промени за засегнатите обекти.
  3. Обновете оригиналните стойности на токена за едновременност, за да отразите текущите стойности в базата данни.
  4. Повторете процеса, докато не възникнат конфликти.

В следващия пример Person.FirstName и Person.LastName са настроени като токени за едновременност. Има // TODO: коментар в местоположението, където включвате специфична за приложението логика, за да изберете стойността, която да бъде запазена.

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.ExecuteSqlCommand(
    "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 handleconcurrency conflicts for " + entry.Metadata.Name);
                }
            }
        }
    }
}