【问题标题】:Using only one SaveChanges call doesn't seem to update my database仅使用一个 SaveChanges 调用似乎不会更新我的数据库
【发布时间】:2020-09-30 09:19:11
【问题描述】:

我在我的方法中做了几件事;就我而言,它们是必要的,但优化代码不是这个问题的目的。

在这种方法中,我创建了一个用户,将用户添加到一个角色,创建一个 Directorate 并在 DirectorateUsers 表中创建一条记录以将用户链接到新的 Directorate。

这里有一些数据库操作,所以我想尝试通过只调用一次 SaveChanges 来减少数据库的负载。

它似乎没有做任何事情;我没有看到添加新的董事,也没有添加董事用户。但是,它会创建用户并将其添加到指定的角色。

是否可以通过这种方式在 Entity Framework 中批量更改数据,还是每次我执行添加或更新记录等操作时都必须await db.SaveChangesAsync()

[HttpPost]
public async Task<ActionResult> Create([Bind(Include = "MunicipalityId,DirectorateName,UserEmailAddress, UserPassword")] RegisterDirectorateViewModel model)
{
    try
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.UserEmailAddress, Email = model.UserEmailAddress };
            var createUserResult = await UserManager.CreateAsync(user, model.UserPassword);
                
            if (createUserResult.Succeeded)
            {
                // Add the user to the directorate role.
                await UserManager.AddToRoleAsync(user.Id, nameof(SystemRoles.Directorate));

                // Generate the directorate and add the user to it.
                var municipality = await db.Municipalities.FindAsync(model.MunicipalityId);
                var directorate = new Directorate
                {
                    Action = MetaAction.Create,
                    ActionBy = user,
                    ActionDate = DateTime.Now,
                    Municipality = municipality,
                    Name = model.DirectorateName
                };
                db.Directorates.Add(directorate);

                var directorateUser = new DirectorateUser
                {
                    Directorate = directorate,
                    User = user
                };
                db.DirectorateUsers.Add(directorateUser);

                // Expire the token so that it can't be used again.
                municipality.TokenExpiryDate = DateTime.Now;
                db.Entry(municipality).State = EntityState.Modified;
                await db.SaveChangesAsync();

                // Sign in the user and redirect to the dashboard.
                await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
                return RedirectToAction("Index", "Dashboard");
            }
        }
        return View(model);
    }
    catch (Exception ex)
    {
        TempData["err"] = ex;
        return RedirectToAction("Create");
    }
}

编辑

这是每个 cmets 的额外模型...

public class Directorate
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Municipality Municipality { get; set; }
    public ApplicationUser ActionBy { get; set; }
    public DateTime ActionDate { get; set; }
    public MetaAction Action { get; set; }
}
public class DirectorateUser
{
    public int Id { get; set; }
    public virtual Directorate Directorate { get; set; }
    public virtual ApplicationUser User { get; set; }
}
public class SubdirectorateUser
{
    public int Id { get; set; }
    public virtual Subdirectorate Subdirectorate { get; set; }
    public virtual ApplicationUser User { get; set; }
}

【问题讨论】:

  • 使用单个 SaveChanges 或 SaveChangesAsync 可以工作,并保存所有批量更改。毫无疑问。你的代码可能会抛出吗? catch 块中没有日志记录,因此任何可能的异常都可能丢失。
  • 发布一个 minimal 示例来演示该问题 - 只是一个控制台应用程序、创建新 DbContext 的必要类和代码、添加一些实体然后调用 SaveChanges
  • @PanagiotisKanavos 完全没有错误;执行通过仪表板重定向并且永远不会进入 catch 块。不贴9000行代码,怎么发这样的例子来演示?
  • 如果DirectorateDirectoryUser 实体只有此处显示的字段,我描述的是大约 10 行和另外 15 行
  • 添加了模型...虽然保留专有信息有一些话要说...好吧...专有的。

标签: c# asp.net-mvc entity-framework


【解决方案1】:

你需要多对多的关系。虽然您有一个 User 表和 Directorates 表,但 DirectorateUsers 表包含 User/ApplicationUser 和 Directorates 之间的多对多关系。所以你必须为多对多关系定制模型。

public class ApplicationUser
{
    public ApplicationUser() 
    {
        this.Directorates = new HashSet<Directorate>();
    }
....
    public virtual ICollection<Directorate> Directorates { get; set; }
}

董事会模型有

public class Directorate
{
    public Directorate()
    {
        this.Users = new HashSet<ApplicationUser>();
    }
    public virtual ICollection<ApplicationUser> Users{ get; set; }
}

现在 DbContext 类看起来像...

public class AppDBContext : DBContext
{
    public AppDBContext() : base("DefaultConnectionString")
    {
    }

    public DbSet<ApplicationUser> Users{ get; set; }
    //or public DbSet<User> Users{ get; set; } //if ApplicationUser doesn't work
    public DbSet<Directorate> Directorates{ get; set; }
        
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //Configure a Many-to-Many Relationship using Fluent API
            modelBuilder.Entity<User>() //or ApplicationUser
            .HasMany<Directorate>(s => s.Directorates)
            .WithMany(c => c.Users)
            .Map(cs =>
                    {
                        cs.MapLeftKey("UserId");
                        cs.MapRightKey("DirectorateId");
                        cs.ToTable("DirectorateUser");
                    });
    }
}

这种映射建立了良好的关系。请查看以下链接了解多对多关系。 https://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx

现在检查您的代码 db.SaveChangesAsync();或者没有等待只是 db.SaveChanges().. 希望这可以工作。记住你必须以正确的方式映射你的对象。

【讨论】:

    【解决方案2】:

    我看到,当您创建 Directorate 和 DirectorateUser 时,您使用的是“user”变量,它可能不是指数据库中的那个。

    使用以下变量而不是“user”来创建 Directorate,DirectorateUser 可以解决问题。

    var userDb = await _userManager.FindByNameAsync(user.UserName)
    

    【讨论】:

    • 行为没有变化。有趣的是,SQL Profiler 不会在 POST 操作中捕获对数据库的任何查询
    【解决方案3】:

    对我来说,这个问题指向了这一点。

                db.Entry(municipality).State = EntityState.Modified;
                await db.SaveChangesAsync();
    

    这只会启动保存与市政当局相关的更改。

    但是,我认为你应该有这样的东西。

    //set all objects that need to be updated in a modified state
                    db.Entry(municipality).State = EntityState.Modified;
                    db.Entry(directorate).State = EntityState.Modified;
                    db.Entry(directorateUser).State = EntityState.Modified;
    //finally save all the changes to the database.
                    await db.SaveChangesAsync();
    

    就是这样,我会这样做的。

    【讨论】:

    • SaveChangesAsync 实际上并没有将任何命令推送到 SQL Server。昨晚看到,当我意识到我可以检查 SQL Profiler 发生了什么时。
    • 那么,我不知道该说什么了。这段代码是从我的一个项目中挑选出来的,当我输入它时它就在服务器上运行,它运行良好。看起来你的问题超出了我的工资等级:P
    【解决方案4】:

    我看到您正在使用UserManager 访问IdentityDbContext。 Identity 框架使用IUserStore 的实例将两者粘合在一起。但正如您所注意到的,每个操作都会立即保存更改。

    默认实现 UserStore 已经有一个布尔属性 AutoSaveChanges 以防止保存每个操作,但是似乎没有明显的方法来访问此属性。

    您可以将IUserStore 服务替换为您自己的实现(根据UserManager's AutoSaveChanges in .NET Core 2.1);

    public class CustomUserStore : UserStore<IdentityUser>
    {
        public CustomUserStore(ApplicationDbContext context)
            : base(context)
        {
            AutoSaveChanges = false;
        }
    }
    services.AddScoped<IUserStore<IdentityUser>, CustomUserStore>();
    

    尽管您随后需要确保所有 UserManager / SigninManager 调用后跟另一个显式保存。

    或者您可以添加 IUserStore 作为依赖项,假设它是 UserStore 的一个实例,并更改您的方法周围的 AutoSaveChanges 值;

    private UserStore<IdentityUser, IdentityRole, DbContext> store;
    public Controller(IUserStore<IdentityUser> store)
    {
        this.store = store as UserStore<IdentityUser, IdentityRole, DbContext>;
    }
    public async Task<ActionResult> Create(...){
        try{
            store.AutoSaveChanges = false;
            ...
        }finally{
            store.AutoSaveChanges = true;
        }
    }
    

    请注意,您需要哪种 UserStore 泛型类型取决于您使用的 IdentityContext 泛型类型。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多