0

I'm looking for good explanation of https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0&tabs=visual-studio

My case is PUT endpoint. I need to update entity in database with optimistic locking. I don't want to expose all properties to user. Only some of them are valid but not all. For example I don't want user to modify date of update.

  1. Should I put it in BeginTrasaction/Commit block?
  2. Is my code good enough?
  3. Why do I need variable in route when I have dto in body?
class MyEntity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long? Id {get; set;}
    public string P1 {get; set;}
    public string P2  {get; set;}
    public DateTime? UpdatedAt {get; set;}
    public Guid ConcurrencyStamp {get; set;} = Guid.NewGuid(); 
}


[HttpPut]
        [Route("{id}")]
        public async Task<IActionResult> Update([FromRoute]long? id, [FromBody]SomeUpdateDTO model)
        {
            if (id != model.Id)
                return BadRequest();
            try
            {
                if (!await dbContext.MyEntities.Where(e => e.Id == id).AnyAsync())
                    return NotFound();
                else
                {
                    var entity = new MyEntity()
                    {
                        Id = id,
                        P1 = model.P1,
                        P2 = model.P2,
                        UpdatedAt = DateTime.UtcNow,
                        ... and some properties that cannot be updated by external DTO like UpdatedAt
                        ConcurrencyStamp = Guid.NewGuid(),
                    };
                    dbContext.Attach(entity);
                    dbContext.Entry(entity).Property(e => e.P1).IsModified = true;
                    dbContext.Entry(entity).Property(e => e.P2).IsModified = true;
                    dbContext.Entry(entity).Property(e => e.UpdatedAt).IsModified = true;
                    dbContext.Entry(entity).Property(e => e.ConcurrencyStamp).IsModified = true;
                    dbContext.Entry(entity).Property(e => e.ConcurrencyStamp).OriginalValue = model.ConcurrencyStamp;
                    await dbContext.SaveChangesAsync();
                    return base.NoContent();
                }
            }
            catch (DbUpdateConcurrencyException) when (!dbContext.MyEntities.Any(e => e.Id == id))
            {
                return NotFound();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Conflict();
            }
  • For optimistic locking, you need to pass the current value of the `ConcurrencyStamp` to the user, who should then supply it back again. To simplify your code, `.Attach` the object with the user supplied value. Then modify it to a new value. – Jeremy Lakeman Sep 20 '22 at 05:28

1 Answers1

0

If you wanna use optimistic locking in your code, It's no need to put the code in BeginTrasaction/Commit. You need to set one property as ConcurrencyToken. From the code you provided, I think you can set ConcurrencyStamp as ConcurrencyToken:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //.........

    modelBuilder.Entity<MyEntity>().Property(x => x.ConcurrencyStamp).IsConcurrencyToken();
}

If you are using SqlServer, You can set a property of type byte[] as RowVersion

class MyEntity
{
    //........
    public byte[] RowVersion { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //...........
     modelBuilder.Entity<MyEntity>().Property(x => x.RowVersion).IsRowVersion();
}

in this case, When you change data, The data in column RowVersion in the database will change automatically, you don't have to change it manually.

Your code is very good, If you choose to change entity's state in Ef Core, You need to be more careful as it is more likely to cause bugs than using the normal method.

Regarding your third question, you can refer to this issue, which has a very detailed explanation and discussion.

Xinran Shen
  • 8,416
  • 2
  • 3
  • 12