简体   繁体   中英

Can't update IdentityUser - double SQL update

I've got an angular website with Mvc's REST Api. I have just updated it from .net core 2.0 to the newest .Net 6, along with EF Core and AspNetCore.Identity.

I have an extended the AspNetCore's IdentityUser. When I try to update it, there are 2 update requests sent (I found that out using the Sql Server Profiler) - one containing the updated column and the another one resetting it back to the original value. It happens only to the IdentityUser, other entities work normally. As a result, I can't update any user.

They can occasionally come in different order so sometimes the update does work (but more often doesn't).

e.g. when I try something like this

        var user = await UserAccountManager.UserManager.FindByIdAsync(id);
        user.Name = model.Name;
        var result = await UserAccountManager.UserManager.UpdateAsync(user);

After this I would see something like this in the profiler: profiler - 2 updates

As you can see, there are 2 subsequent updates which differ by the Name field and the ConcurrencyStamp.

I've tried to just fetch the user directly from the context e.g.:

                var xx = await Context.Users.SingleOrDefaultAsync(m => m.Id == id);
                xx.Name = model.Name;
                var aa = await Context.SaveChangesAsync();

Same thing.

Even wrapping it all in a transaction didn't work - the SQL requests are separated out but after updating the user there is still another SQL query sent that reverts it back.

I'm pasting in the ConfigureServices function (from Startup.cs) if that helps:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().AddNewtonsoftJson(options => {
                                             options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                                             options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                                         });

        services.AddDbContext<OffWorkDbContext>(options =>
                                                    {
                                                        options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"], b => b.MigrationsAssembly("Hicron.OffWorkPlanner.DataAccessComponent"));
                                                    });

        // add identity
        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<OffWorkDbContext>()
            .AddDefaultTokenProviders();

        services.Configure<IdentityOptions>(o => 
                                            {
                                                // User settings
                                                o.User.RequireUniqueEmail = true;
                                                o.Password.RequireDigit = false;
                                                o.Password.RequireNonAlphanumeric = false;
                                                o.Password.RequireUppercase = false;
                                                o.Password.RequireLowercase = false;
                                                //o.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
                                                //o.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
                                                //o.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
                                            });

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
                          {
                              options.TokenValidationParameters = new TokenValidationParameters
                                                                  {
                                                                      ValidateIssuer = true,
                                                                      ValidateAudience = true,
                                                                      ValidateLifetime = true,
                                                                      ValidateIssuerSigningKey = true,
                                                                      ValidIssuer = Configuration["Token:Issuer"],
                                                                      ValidAudience = Configuration["Token:Issuer"],
                                                                      IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Token:Key"]))
                                                                  };
                          });

        services.AddAuthorization(options =>
                                  {
                                      //add authorization policies 
                                      options.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                                          .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                                          .RequireAuthenticatedUser().Build());
                                  });

        Mapper.Initialize(cfg =>
                          {
                              cfg.AddProfile<AutoMapperProfile>();
                          });


        // Add cors
        services.AddCors();

        // Add framework services.
        services.AddMvc(options =>
        {
            options.EnableEndpointRouting = false;
        });


        // In production, the Angular files will be served from this directory
        services.AddSpaStaticFiles(configuration =>
                                   {
                                       configuration.RootPath = "ClientApp/dist";
                                   });

        services.Configure<EmailConfig>(Configuration.GetSection("SmtpConfig"));
        services.AddScoped<IEmailNotifications, EmailNotifications>();
        services.AddScoped<IUserAccountManager, UserAccountManager>();
        services.AddScoped<ITeamService, TeamService>();
        services.AddScoped<IUserService, UserService>();
        services.AddScoped<IDayService, DayService>();
        services.AddScoped<IProjectService, ProjectService>();
        services.AddScoped<IUserCalendarItemService, UserCalendarItemService>();
        services.AddScoped<IDepartmentService, DepartmentService>();
        services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
    }

Please help me figure out what's going on here (and update the user).

Ok, it turned out there were 2 concurrent requests - the 2nd one being to update roles and even though the one for roles didn't explicitly update user, it did change it concurrency timestamp behind the scenes and for some reason it was doing it with outdated values.

To fix this we just brought these 2 requests together.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM