EF Core concurrency control

Concurrent token

The property is configured to implement token concurrent optimistic concurrency control

NOTES

Annotation data using ConcurrencyCheckAttributethe attribute token is configured to concurrently

public class Person
{
    [Key]
    public int Id { get; set; }

    [ConcurrencyCheck]
    [MaxLength(32)]
    public string FirstName { get; set; }

    [MaxLength(32)]
    public string LastName { get; set; }
}

Fluent Api

Using Fluent Api concurrent token configuration properties

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<Person>().Property(s => s.FirstName).IsConcurrencyToken();
}

Timestamp / row version

Generated when adding or updating the database to a new value to an attribute configured timestamp, this attribute is also regarded as a token concurrently. This ensures that you are after (ChangeTracker) line of data query, try to update this line, but the data has been modified by someone else at this time, it will return an exception.

NOTES

Use annotation data TimestampAttributeto a timestamp attribute flag

public class Person
{
    [Key]
    public int Id { get; set; }

    [ConcurrencyCheck]
    [MaxLength(32)]
    public string FirstName { get; set; }

    [MaxLength(32)]
    public string LastName { get; set; }

    [Timestamp]
    public byte[] Timestamp { get; set; }
}

### Fluent Api

Using Fluent Api flag attribute timestamp

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<Person>()
        .Property(s => s.FirstName).IsConcurrencyToken();

    builder.Entity<Person>()
        .Property(s => s.Timestamp).IsRowVersion();
}

Data migration script

Adding migration

 protected override void Up(MigrationBuilder migrationBuilder)
 {
     migrationBuilder.AddColumn<byte[]>(
         name: "Timestamp",
         table: "People",
         rowVersion: true,
         nullable: true);
 }

Take a look at ModelSnapshotthe migration generated

modelBuilder.Entity("LearningEfCore.Person", b =>
                    {
                        b.Property<int>("Id")
                            .ValueGeneratedOnAdd()
                            .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);

                        b.Property<string>("FirstName")
                            .IsConcurrencyToken()
                            .HasMaxLength(32);

                        b.Property<string>("LastName")
                            .HasMaxLength(32);

                        b.Property<byte[]>("Timestamp")
                            .IsConcurrencyToken()
                            .ValueGeneratedOnAddOrUpdate();

                        b.HasKey("Id");

                        b.ToTable("People");
                    });

ValueGeneratedOnAddOrUpdate It represents the data generated automatically upon insertion and Update

Handle concurrency conflicts

How EF Core detect concurrency conflicts

Optimistic concurrency control configured to achieve concurrent entity attribute token: When the update or delete operation in SaveChangesthe event of the process, EF Core the database will be concurrent with the value of the token ChangeTrackervalue is compared tracking.

  • If the two values ​​are identical, can be done
  • If different, EF Core assumes that the user performs other operations with the current conflict, and will terminate the current transaction.

Other operations in conflict with the current user performed by the user is called concurrency conflicts .

Database providers to achieve more concurrent copy token.

In a relational database, EF Core update and delete operations will by WHEREcontaining relatively concurrency token conditional statement, after the statement is executed, the number of rows affected reads EF Core, if no rows affected, will be detected concurrent conflict, EF Core also throwDbUpdateConcurrencyException

We adopted the following example to demonstrate this:

Api to create a controller:

    [Route("api/[controller]")]
    [ApiController]
    public class DefaultController : ControllerBase
    {
        private readonly TestDbContext _db;

        public DefaultController(TestDbContext db)
        {
            _db = db;
        }

        [HttpPost]
        public async Task<Person> Add([FromBody]Person person)
        {
            var entry = await _db.People.AddAsync(person);
            await _db.SaveChangesAsync();

            return entry.Entity;
        }

        [HttpPut]
        public async Task<Person> Update([FromBody]Person current)
        {

            var original = await _db.People.FindAsync(current.Id);
            original.FirstName = current.FirstName;
            original.LastName = current.LastName;
            await _db.SaveChangesAsync();

            return original;
        }
    }

POST method request http://localhost:5000/api/default, body json string using:

{
    "firstName": "James",
    "lastName": "Rajesh"
}

return:

{
    "id": 1,
    "firstName": "James",
    "lastName": "Rajesh",
    "timestamp": "AAAAAAAAB9E="
}

We can see timestampas we wish, automatically generate a concurrency token

Here we try to SaveChangesvalue when modifying the database:

In the Updateinterface await _db.SaveChangesAsync();breakpoint trip.

Modify Request Body as follows:

{
    "id": 1,
    "firstName": "James1",
    "lastName": "Rajesh1",
    "timestamp": "AAAAAAAAB9E="
}

A PUT request mode http://localhost:5000/api/default, the breakpoint is hit,

Modify the database LastNamevalue Rajesh2, then F10, we get the following concurrency exception:

DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.

At this point one might wonder, how EF Core is detected LastNamechanged it? In actual fact, we are modifying the data in the database when, RawVersion column Timestampautomatically updated. And every time we use when EF Core updated statement produced like this (you can see through the console log):

Executed DbCommand (68ms) [Parameters=[@p2='?' (DbType = Int32), @p0='?' (Size = 32), @p3='?' (Size = 32), @p1='?' (Size = 32), @p4='?' (Size = 8) (DbType = Binary)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [People] SET [FirstName] = @p0, [LastName] = @p1 WHERE [Id] = @p2 AND [FirstName] = @p3 AND [Timestamp] = @p4;

Here we use WHEREconditions to determine Timestampwhether the same

Below remove Timestampcolumns, leaving the flag ConcurrencyTokenofFirstName

PUT request using the embodiment http://localhost:5000/api/default, body of:

{
    "id": 1,
    "firstName": "James6",
    "lastName": "Rajesh11"
}

Again at SaveChangesthe time of modifying the database record corresponding to the LastNamevalue of Rajesh19, this case not being given, the return value:

{
    "id": 1,
    "firstName": "James6",
    "lastName": "Rajesh11"
}

The value of the database can be modified to Rajesh11 explanation here is not detected concurrent, let's try to modify. FirstNameIs James12, at the same time SaveChangeswhen amended to Rajesh13, this time it detects concurrency conflicts, we look at the console of the statement:

Executed DbCommand (63ms) [Parameters=[@p1='?' (DbType = Int32), @p0='?' (Size = 32), @p2='?' (Size = 32)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [People] SET [FirstName] = @p0 WHERE [Id] = @p1 AND [FirstName] = @p2;

See here will determine FirstNameand ChangeTrackercompare the value, after the execution of the line is not affected, so will detects concurrency conflicts.

How to handle concurrency conflicts EF Core

First to understand with the help of three sets of values ​​to resolve concurrency conflicts:

  • Current values ​​Current value: the value of an application tries to write to the database
  • Original values ​​original value: EF Core value retrieved from the database is located in any of the value before the update operation
  • Database values ​​database value: The current value is actually stored in the database

When SaveChanges, if captured DbUpdateConcurrencyException, indicating the occurrence of a concurrent conflict, using DbUpdateConcurrencyException.Entriesto prepare a new set of values for the affected entities, retrieve database concurrency token value to refresh Original valuesand try again until no conflicts.

        [HttpPut]
        public async Task<Person> Update([FromBody]Person current)
        {
            Person original = null;

            try
            {
                original = await _db.People.FindAsync(current.Id);
                original.FirstName = current.FirstName;
                original.LastName = current.LastName;
                await _db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException e)
            {
                foreach (var entry in e.Entries)
                {
                    var currentValues = entry.CurrentValues;
                    var databaseValues = await entry.GetDatabaseValuesAsync();

                    if (entry.Entity is Person person)
                    {
                        // 更新什么值取决于实际需要

                        person.FirstName = currentValues[nameof(Person.FirstName)]?.ToString();
                        person.LastName = currentValues[nameof(Person.LastName)]?.ToString();

                        // 这步操作是为了刷新当前 Tracker 的值, 为了通过下一次的并发检查
                        entry.OriginalValues.SetValues(databaseValues);
                    }
                }

                await _db.SaveChangesAsync();
            }

            return original;
        }

This operation can also be added to retry strategy.

At this point, even in SaveChangetime to update the values in the database (or other user changes the value of the same entity), triggering a concurrent conflict, the conflict can be resolved as we want to modify the data.

Code address https://github.com/RajeshJs/Learning/tree/master/LearningEfCore

Guess you like

Origin www.cnblogs.com/rajesh/p/11093193.html