-1

I have a project that I developed with EF Core and react. What I want is to be able to give the second person an "updated before" error if two different users open the screen at the same time and update the same data at different times. But when I apply it as in the pictures, it does not catch any errors and updates the db. A unique new rowversion is also assigned to the db.

implementation of what I want in the console app

dbcontext

appservice screenshot

entity screenshot

rowversion column

The points I updated in my project while implementing.

on modelcreating:

modelBuilder.Entity<User>().Property(e => e.RowVersion)
    .IsRequired()
    .IsRowVersion()
    .IsConcurrencyToken();

entity:

  public class User : AbpUser<User>
    {
        [Timestamp]
        public byte[] RowVersion { get; set; }
}

the code i expect to give an error:

 public override async Task<UserDto> UpdateAsync(UserDto input) {

        CheckUpdatePermission();

        //input.RowVersion = new byte[] { 0, 0, 0, 0, 0, 0, 7, 215 };

        var user = await _userManager.GetUserByIdAsync(input.Id);

        //MapToEntity(input, user);

        //user.UserName = "hh";

        //user.RowVersion = new byte[] { 1, 2, 0, 0, 0, 0, 9, 85};
        user.RowVersion[1] =55;

        try
        {
            CheckErrors(await _userManager.UpdateAsync(user));
        }
        catch (Exception e)
        {
            throw new UserFriendlyException(e.Message);
        }

        if (input.RoleNames != null)
        {
            CheckErrors(await _userManager.SetRolesAsync(user, input.RoleNames));
        }

        return await GetAsync(input);
    }
  • 1
    Post any code or errors as *text*. Images can't be copied, compiled, tested or googled – Panagiotis Kanavos Nov 18 '22 at 10:07
  • Have you checked the Microsoft docs and tutorials? [Handling Concurrency Conflicts](https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations) and [Tutorial: Handle concurrency - ASP.NET MVC with EF Core](https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/concurrency?view=aspnetcore-7.0) ? – Panagiotis Kanavos Nov 18 '22 at 10:10
  • There's no EF code in those pictures. There's no DbContext configuration or table schema. Modifying the `rowversion` *breaks* optimistic concurrency. The `rowversion` value is meant to be modified automatically by the database, not client applications. That's the entire point of optimistic concurrency - if anything else changed the row since it was loaded, the client will reject the changes. Without a `rowversion` EF will compare all column values for differences – Panagiotis Kanavos Nov 18 '22 at 10:18
  • @PanagiotisKanavos A rowversion column is created in the database and it changes automatically in every update process. My problem is that even if I update with an incorrect rowversion, the dbupdateconcurrency error does not appear. thanks – Sahin Kenanoglu Nov 18 '22 at 10:20
  • Again, post text, not images. You haven't posted posted any EF code or DbContext configuration. Perhaps the DbContext doesn't even have optimistic concurrency enabled. ABP isn't Entity Framework, it's a third party library with a lot of quirks. A DbContext is a Unit-of-Work already, so what is that `UnitOfWork(transaction:false)` attribute supposed to do? Have you tried using EF directly? Eg write a unit test or console application, create your DbContext, load an entry, modify the rowversion and save? – Panagiotis Kanavos Nov 18 '22 at 10:29
  • In short EF and optimistic concurrency work. The documentation examples work. What does this question's code do differently? We can't guess – Panagiotis Kanavos Nov 18 '22 at 10:30
  • You can create an example that doesn't require a database if you use the [in-memory provider](https://learn.microsoft.com/en-us/ef/core/testing/testing-without-the-database#in-memory-provider) or SQLite in in-memory mode. This will allow people to copy your code, test it and find out what's wrong and how to fix it – Panagiotis Kanavos Nov 18 '22 at 10:35
  • @PanagiotisKanavos I can capture concurrency by writing a console application. I added unitofwork while experimenting. I don't even think there is a need to write anything to the dbcontext. but to try, I added the picture that I specified as rowversion in dbcontext. still couldn't catch it. – Sahin Kenanoglu Nov 18 '22 at 10:37
  • Post the code instead of describing it. If you think you have an EF problem, write a small example that uses EF code *only* to demonstrate the problem. Don't ask people to type your code and try to guess the missing parts – Panagiotis Kanavos Nov 18 '22 at 10:46
  • @SahinKenanoglu It's a bit frustrating that you keep posting images of code. As explained, there are [good reasons why you shouldn't do that](https://meta.stackoverflow.com/questions/285551/why-not-upload-images-of-code-errors-when-asking-a-question). – Gert Arnold Nov 18 '22 at 10:53
  • @PanagiotisKanavos I didn't ask anyone to write code. I threw out the places I updated to add rowversion for understanding the subject. I'm wondering if I need to update anywhere else to add rowversion and see if it works. – Sahin Kenanoglu Nov 18 '22 at 10:56
  • All that said, the only thing here is that you should change `OriginalValue` of the rowversion property, not the property itself. – Gert Arnold Nov 18 '22 at 11:02
  • The EF Core code you posted (still as image, not as text) works as expected. Which means the problem is in ABP and how it's configured, not EF. – Panagiotis Kanavos Nov 18 '22 at 11:03
  • @GertArnold user.RowVersion[1] =55; Do I need to change the line? Do you have any idea how I can do this in the updateasync method I added? thank you. – Sahin Kenanoglu Nov 18 '22 at 11:09
  • Something like `context.Entry(user).Property(x => x.RowVersion).OriginalValue = new byte[8]`. – Gert Arnold Nov 18 '22 at 11:33

2 Answers2

0

If you are using ABP framework (and you use the framework supplied EfCoreRepository base class for your repository implementation) this is all handled for you when your entity implements the IHasConcurrencyStamp interface. Use this instead of RowVersion.

Make sure you are assigning the concurrency stamp value to the entities ConcurrencyStamp property (supplied by the caller) before calling update on the repository/DbContext.

  • https://docs.abp.io/en/abp/latest/Concurrency-Check https://community.abp.io/posts/handle-concurrency-with-ef-core-in-an-abp-framework-project-with-asp.net-core-mvc-jlkc3w8f I used the articles. but when I added the Ihasconcurrency interface to my project, it still didn't work. I tested it while updating it giving any wrong text. – Sahin Kenanoglu Nov 18 '22 at 10:38
  • When I apply these documents, a concurrencystamp column is created in the database that changes with each update operation. However, even if I give a wrong stamp value while updating, the update process is performed without any error. – Sahin Kenanoglu Nov 18 '22 at 10:42
  • Are you using the ABP DbContext and repository implementations? – Jason Kingsmill Nov 18 '22 at 10:45
  • yes. DevFrameTemplateDbContext : AbpZeroDbContext – Sahin Kenanoglu Nov 18 '22 at 10:52
  • Are you using ABP Framework or ABP Zero? That DbContext is not from ABP framework. MyDbContext : AbpDbContext is what it should look like for ABP framework – Jason Kingsmill Nov 18 '22 at 11:03
  • ım using using ABP Framework – Sahin Kenanoglu Nov 18 '22 at 11:13
  • OK it sounds like this is where your problem is then...you'll need to remove references to ABPZero, and replace with the relevant ABP packages – Jason Kingsmill Nov 18 '22 at 11:30
0

When I edited the userappservice I specified in the question as follows, rowversion threw the error and my problem was solved. But I don't know if updating with such a method instead of updateasync will cause any problems.

[UnitOfWork(isTransactional:false)]
        public override async Task<UserDto> UpdateAsync(UserDto input)
        {
            CheckUpdatePermission();

            var user = await _userManager.GetUserByIdAsync(input.Id);

            var options = new DbContextOptionsBuilder<DevFrameTemplateDbContext>()
                   .UseSqlServer(new SqlConnection("customsqlconnectıonstring"))
                   .Options;

            using (DevFrameTemplateDbContext db = new
                DevFrameTemplateDbContext(options))
            {
                User p = await db.Users.AsNoTracking().FirstAsync(x => x.Id == input.Id);
                p.UserName = "Guncellendiiiiiiii";
                p.RowVersion = new byte[] { 0, 0, 0, 0, 0, 0, 39, 22 }; // hatalı verinde error. doğru verince kaydediyor.
                db.Users.Update(p);
                await db.SaveChangesAsync();
            }

            MapToEntity(input, user);

            //try
            //{
                //CheckErrors(await _userManager.UpdateAsync(user));
            //}
            //catch (Exception e)
            //{
                //throw new UserFriendlyException(e.Message);
            //}

            if (input.RoleNames != null)
            {
                CheckErrors(await _userManager.SetRolesAsync(user, input.RoleNames));
            }

            return await GetAsync(input);
        }

Looking forward to your comments to make this working method short enough.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291