Дополнительные статьи

Параллелизм

Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core 7

Последнее обновление: 18.11.2020

В ситуации, когда множество пользователей одновременно имеют доступ к одинаковому набору данных и могут эти данные изменять, мы можем столкнуться с проблемой параллелизма. Например, два пользователя независимо друг от друга начнут редактировать один и тот же объект. И после сохранения объекта первым пользователем второй пользователь уже будет работать с неактуальными данными.

Для решения проблемы параллелизма Entity Framework Core предлагает использовать токены параллелизма или concurrency token.

Если свойство установлено в качестве concurrency token (токен параллелизма), Entity Framework перед сохранением изменений будет проверять его значение. Если значение отличается, то какой-то другой пользователь уже произвел изменения над данными. В этом случае EF Core применяет принцип оптимистичного параллелизма (optimistic concurrency), при котором, если сохраняемые данные уже были кем-то изменены, то выбрасывается ошибка.

Атрибут ConcurrencyCheck

Атрибут ConcurrencyCheck позволяет решить проблему параллелизма, когда с одной и той же записью в таблице могут работать одновременно несколько пользователей. Например:

public class User
{
	public int Id { get; set; }
	[ConcurrencyCheck]
    public string Name { get; set; }
}
public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureCreated();
	}
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
    }
}

В обычном режиме Entity Framework Core при обновлении смотрит на Id и если Id записи в таблице совпадает с Id в передаваемой модели User, то строка в таблице обновляется. При использовании атрибута ConcurrencyCheck EF смотрит не только на Id, но и на исходное значение свойства Name. И если оно совпадает с тем, что имеется в таблице, то запись обновляется. Если же не совпадает (то есть кто-то уже успел обновить), то EF генерирует исключение DbUpdateConcurrencyException:

using (ApplicationContext db = new ApplicationContext())
{
    try
    {
        User user = db.Users.FirstOrDefault();
        if(user !=null)
        {
            user.Name = "Bob";
            db.SaveChanges();
        }
    }
    catch(DbUpdateConcurrencyException ex)
    {
	}
}

Метод IsConcurrencyToken

В Fluent API токен параллелизма настраивается с помощью метода IsConcurrencyToken():

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureCreated();
	}
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().Property(b => b.Name).IsConcurrencyToken();
    }
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
    }
}
public class User
{
	public int Id { get; set; }
    public string Name { get; set; }
}

Атрибут Timestamp

Другой механизм по отслеживанию изменений объекта в БД представляет атрибут Timestamp.

public class User
{
	public int Id { get; set; }
    public string Name { get; set; }
	
	[Timestamp]
    public byte[] Timestamp { get; set; }
}

Атрибут Timestamp указывает, что значение свойства Timestamp будет включаться в создаваемое Entity Frameworkом SQL-выражение WHERE при отправке в базу данных команд на обновление и удаление. То есть значение данного свойства генерируется при добавлении объекта, а также каждый раз, когда пользователь будет обновлять его. В качестве типа для свойства используется массив байтов.

И если два пользователя одновременно начнут редактировать одну и ту же строку, то после сохранения модели первым пользователем, второй пользователь получит исключение DbUpdateConcurrencyException, которое соответственно надо обработать.

Вместо атрибута мы можем использовать Fluent API:

public class User
{
	public int Id { get; set; }
	public string Name { get; set; }
	public byte[] Timestamp { get; set; }
}
public class ApplicationContext : DbContext
{
	public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureCreated();
	}
	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<User>().Property(b => b.Timestamp).IsRowVersion();
	}
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
	}
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850