Optimictics Handling Concurrency Conflicts doesn't work!

sblb 1,231 Reputation points
2023-09-15T07:59:05.4666667+00:00

Hi, my application is made with blazor wasm. I try to implement the optimistic Concurrency.

I follow up the tutorial from mricosoft https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/concurrency?view=aspnetcore-7.0&tabs=visual-studio

but I didn't understand clear this mechanism.

I use the Timestemp in class model. After I open two browsers page to test handling concurrency but I didn't see any error message for all fields that don't match the database values:

The classe that I use :

  public class ProjetModel 
    {
        [Key]
        public int IdProjet { get; set; }
        [Required]
        public DateTime DateProjet { get; set; }
        [Required]
        public string? Title { get; set; }        
        public string? Type { get; set; }
        public string? Level { get; set; }
        public bool? IsCompleted { get; set; }
        public string? Statut { get; set; }
        public int ProjetCount { get; set; }
        public string? ProjetCountId {
            get
            {
                return $"{ProjetCount.ToString().PadLeft(3, '0')}";
            }
            set { }
        }

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

        public List<ActionItemProjet>? ActionItemProjets { get; set; }
    }

I've tested two PUT method

First test

        [HttpPut]
        public async Task<IActionResult> Put(ProjetModel projet)
        { 

            _context.Entry(projet).State = EntityState.Modified;
            await _context.SaveChangesAsync();
            return NoContent();
        }

EF didn't throw an error. If I understand in this context, EF code will compare the timestamp to the one in the database and show error if no match.

Second test : it here that i tried to follow up the Microsoft tutorial

[HttpPut]
        public async Task<IActionResult> Put(ProjetModel projet)
        {
            var toUpdate = await _context.ProjetModels.FirstOrDefaultAsync(x => x.IdProjet == projet.IdProjet);

            _context.Entry(toUpdate).Property(d => d.ConcurrencyToken).OriginalValue = projet.ConcurrencyToken;

            if (await TryUpdateModelAsync<ProjetModel>(
                toUpdate,
               "Projet",
                s => s.Title, s => s.Type, s => s.Statut, s => s.DateProjet))
            {
                try
                {
                    await _context.SaveChangesAsync();
                    return RedirectToPage("./ppage");
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    var exceptionEntry = ex.Entries.Single();
                    var clientValues = (ProjetModel)exceptionEntry.Entity;
                    var databaseEntry = exceptionEntry.GetDatabaseValues();
                    if (databaseEntry == null)
                    {
                        ModelState.AddModelError(string.Empty, "Unable to save. " +
                            "The line was deleted by another user.");
                        return RedirectToPage("./ppage");

                    }
                    var dbValues = (ProjetModel)databaseEntry.ToObject();
                    await SetDbErrorMessage(dbValues, clientValues);
                    // Save the current ConcurrencyToken so next postback
                    // matches unless an new concurrency issue happens.
                    projet.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
                    ModelState.Remove($"{nameof(projet)}.{nameof(projet.ConcurrencyToken)}");
                }
                
            }
            return RedirectToPage("./ppage");
            // _context.Entry(projet).State = EntityState.Modified;
            // await _context.SaveChangesAsync();
            // return NoContent();
        }

        private async Task SetDbErrorMessage(ProjetModel dbValues,
                                         ProjetModel clientValues)
            {

                    if (dbValues.Title != clientValues.Title)
                    {
                        ModelState.AddModelError("Department.Name",
                            $"Current value: {dbValues.Title}");
                    }
                    if (dbValues.Type != clientValues.Type)
                    {
                        ModelState.AddModelError("Department.Budget",
                            $"Current value: {dbValues.Type:c}");
                    }
                    if (dbValues.DateProjet != clientValues.DateProjet)
                    {
                        ModelState.AddModelError("Department.StartDate",
                            $"Current value: {dbValues.DateProjet:d}");
                    }
                    if (dbValues.Statut != clientValues.Statut)
                    {
                        ModelState.AddModelError("Department.StartDate",
                            $"Current value: {dbValues.Statut:d}");
                    }
            ModelState.AddModelError(string.Empty,
                                       "The record you attempted to edit "
                                     + "was modified by another user after you. The "
                                     + "edit operation was canceled and the current values in the database "
                                     + "have been displayed. If you still want to edit this record, click "
                                     + "the Save button again.");
        }

In case the put method doesn't work.

I really need some help i) to understand the mechanism of optimistic and ii) help me to get the modification of put method

Thanks in advance to your help

Developer technologies | .NET | Blazor
0 comments No comments
{count} votes

Accepted answer
  1. AgaveJoe 30,126 Reputation points
    2023-09-15T13:39:19.7066667+00:00

    I really need some help i) to understand the mechanism of optimistic and ii) help me to get the modification of put method

    It is impossible to know what you are thinking, what you know, what you don't know... What I do know from your many threads is the subject of your posts are never the actual problem. Maybe your test is faulty or some other problem.

    Anyway, I wrote an example based on your code and the [Timestamp] attribute functions exactly as documented.

    Model

        public class ProjetModel
        {
            [Key]
            public int ProjetId { get; set; }
            [Required]
            public DateTime DateProjet { get; set; }
            [Required]
            public string? Title { get; set; }
            public string? Type { get; set; }
            public string? Level { get; set; }
            public bool? IsCompleted { get; set; }
            public string? Statut { get; set; }
            public int ProjetCount { get; set; }
            public string? ProjetCountId
            {
                get
                {
                    return $"{ProjetCount.ToString().PadLeft(3, '0')}";
                }
                set { }
            }
    
            [Timestamp]
            public byte[]? ConcurrencyToken { get; set; }
    
            public List<ActionItemProjet>? ActionItemProjets { get; set; }
        }
    
        public class ActionItemProjet
        {
            [Key]
            public int ActionItemProjetId { get; set; }
            public string ActionItemProjetName { get; set;}
    
            public int? ProjectId { get; set; }
            public ProjetModel? ProjetModel { get; set; }
        }
    

    I let Visual Studio scaffold the controller and I modified the GET actions to Include the ActionItemProjets collection.

        [Route("api/[controller]")]
        [ApiController]
        public class ProjetModelsController : ControllerBase
        {
            private readonly ApplicationDbContext _context;
    
            public ProjetModelsController(ApplicationDbContext context)
            {
                _context = context;
            }
    
            // GET: api/ProjetModels
            [HttpGet]
            public async Task<ActionResult<IEnumerable<ProjetModel>>> GetProjetModels()
            {
              if (_context.ProjetModels == null)
              {
                  return NotFound();
              }
                return await _context.ProjetModels
                    .Include(a =>a.ActionItemProjets).ToListAsync();
            }
    
            // GET: api/ProjetModels/5
            [HttpGet("{id}")]
            public async Task<ActionResult<ProjetModel>> GetProjetModel(int id)
            {
              if (_context.ProjetModels == null)
              {
                  return NotFound();
              }
                var projetModel = await _context.ProjetModels
                    .Include(a => a.ActionItemProjets)
                    .SingleOrDefaultAsync(p => p.ProjetId == id);
    
                if (projetModel == null)
                {
                    return NotFound();
                }
    
                return projetModel;
            }
    
            // PUT: api/ProjetModels/5
            // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
            [HttpPut("{id}")]
            public async Task<IActionResult> PutProjetModel(int id, ProjetModel projetModel)
            {
                if (id != projetModel.ProjetId)
                {
                    return BadRequest();
                }
    
                _context.Entry(projetModel).State = EntityState.Modified;
    
                try
                {
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!ProjetModelExists(id))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
    
                return NoContent();
            }
    
            // POST: api/ProjetModels
            // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
            [HttpPost]
            public async Task<ActionResult<ProjetModel>> PostProjetModel(ProjetModel projetModel)
            {
              if (_context.ProjetModels == null)
              {
                  return Problem("Entity set 'ApplicationDbContext.ProjetModels'  is null.");
              }
                _context.ProjetModels.Add(projetModel);
                await _context.SaveChangesAsync();
    
                return CreatedAtAction("GetProjetModel", new { id = projetModel.ProjetId }, projetModel);
            }
    
            // DELETE: api/ProjetModels/5
            [HttpDelete("{id}")]
            public async Task<IActionResult> DeleteProjetModel(int id)
            {
                if (_context.ProjetModels == null)
                {
                    return NotFound();
                }
                var projetModel = await _context.ProjetModels.FindAsync(id);
                if (projetModel == null)
                {
                    return NotFound();
                }
    
                _context.ProjetModels.Remove(projetModel);
                await _context.SaveChangesAsync();
    
                return NoContent();
            }
    
            private bool ProjetModelExists(int id)
            {
                return (_context.ProjetModels?.Any(e => e.ProjetId == id)).GetValueOrDefault();
            }
        }
    

    Swagger is used to test EF's optimistic concurrency . First the GET by Id action is called to get ProjetId = 1.

    [
      {
        "projetId": 1,
        "dateProjet": "2023-09-15T07:37:13.8697323",
        "title": "concurrency test 1",
        "type": "Type 1",
        "level": "Level 1",
        "isCompleted": false,
        "statut": "Statut 1",
        "projetCount": 0,
        "projetCountId": "000",
        "concurrencyToken": "AAAAAAAAB9I=",
        "actionItemProjets": [
          {
            "actionItemProjetId": 1,
            "actionItemProjetName": "ActionItemProjetName 1",
            "projectId": null,
            "projetModel": null
          },
          {
            "actionItemProjetId": 2,
            "actionItemProjetName": "ActionItemProjetName 2",
            "projectId": null,
            "projetModel": null
          }
        ]
      }
    ]
    

    Next, the concurrencyToken is changed to AAAAAAAAB9A= to force a concurrency exception. Finally, the payload is summitted to the PUT action. The result is an exception as expected.

    Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.

    Perhaps try debugging your code and paying closer attention to what you're doing and what the code is doing.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.