无法使用 EFCore、EntityState 编辑数据库条目。已修改: "Database operation expected to affect 1 row(s) but actually af



我正在使用带有 ASP.NET MVC Core 1.0 和实体框架核心 1.0 的身份核心 1.0 来创建一个简单的用户注册系统,并以本文为起点,我正在尝试添加用户角色。我可以添加用户角色,但无法编辑它们。以下是RolesController中的Edit操作:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Edit(IdentityRole role)
    {
        try
        {
            _db.Roles.Attach(role);
            _db.Entry(role).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
            _db.SaveChanges();
            return RedirectToAction("Index");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            return View();
        }
    }

下面是相应视图中的表单:

@model Microsoft.AspNet.Identity.EntityFramework.IdentityRole
@{
    ViewBag.Title = "Edit";
}
<h2>Edit Role</h2>
<hr />
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    @Html.HiddenFor(model => model.Id)
    <div>Role name</div>
    <p>@Html.TextBoxFor(model => model.Name)</p>
    <input type="submit" value="Save" />
}

新角色名称不会保存到数据库中,并且我收到以下异常:Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.

我能够使用此确切的代码(使用Microsoft.AspNet.Identity.EntityFramework依赖项而不是EntityFrameworkCore(使用 EF 7、标识 3 等编辑数据库条目。

关于为什么此代码不允许修改数据库条目的任何想法?

原因在例外中明确说明。

Edit操作中收到role对象时检查该对象的Id,并尝试在数据库中查找该 ID。您看到的异常消息指出,它期望找到具有您附加的对象匹配 ID 的行,但事实并非如此,因此它无法执行更新,因为它找不到匹配的行来更新它。

编辑:

您将附加实体两次,删除对.Attach(role)的调用,并保留其下方的行,这足以将对象添加到处于修改状态的跟踪上下文中。

//_db.Roles.Attach(role); //REMOVE THIS LINE !.
_db.Entry(role).State = Microsoft.EntityFrameworkCore.EntityState.Modified;

请注意,将条目的状态设置为已修改将在调用.SaveChanges()时更新所有属性值,因此,如果您只想更新某些属性,请参阅此答案。

如果这不能解决您的问题,请检查您可能遗漏的任何内部异常。有时异常消息没有意义,并掩盖了您可能能够在内部异常中找到的真正问题。

如果您尝试更新具有新相关实体的实体,也可能发生这种情况。

例如,此代码将引发异常:

someEntity.AnotherEntity = new AnotherEntity();
dbContext.Update(someEntity);
dbContext.SaveChanges(); // Exception

相反,请执行此操作:

someEntity.AnotherEntity = new AnotherEntity();
dbContext.AnotherEntitySet.Add(someEntity.AnotherEntity);
dbContext.Update(someEntity);
dbContext.SaveChanges(); // No Exception

您可以尝试如下所示。

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(IdentityRole role)
{
    try
    {
         _db.Entry(role).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
        return View();
    }
}

注意:

_db.Entry(role).State = EntityState.Modified;

  • 您不仅将实体附加到_db,而且还标记了整个实体作为dirty.
  • 当您执行_db.SaveChanges() 时,EF 将生成一个update声明will update all the fields of the entity.

_db.Roles.Attach(role)

  • 将实体附加到上下文,而不将其标记为脏
  • 就像_db.Entry(role).State = EntityState.Unchanged;.
  • 除非你接着继续update a property on the entity,否则下次调用context.SaveChanges()时,EF不会为这个entity生成database update

,如果您需要生成数据库更新,则必须这样做:

_db.Roles.Attach(role); // State = Unchanged
role.RoleName = "Admin"; // State = Modified, and only the RoleName property is dirty
context.SaveChanges();

执行更新或删除后,EF Core 会读取受影响的行数。

SELECT [ExampleEntityId]
FROM [ExampleEntities]
WHERE @@ROWCOUNT = 1 AND
[ExampleEntityId] = scope_identity();

如果实体没有作为主键的标识列,EF Core 将引发 DbUpdateConcurrencyException。

就我而言,我将主键(并将它们设置为标识(添加到数据库中的相关表中。

你的答案对我不起作用。我就这样解决了我的错误。更改的模型类属性

[Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public string Id { get; set; }

更改后 我的映射类

builder.Property(c => c.Id).HasColumnName("ID").IsRequired();

最后的更改是

 CustomerEntity thisrole = (from x in db.Customers
                          where x.Id == Id
                          select x).First();
            thisrole.Id = accountNum;
            thisrole.Name = name;
            thisrole.Phone = phone;
            thisrole.Email = email;
            thisrole.Address = address;
            db.SaveChanges();              
            return true;

我希望此解决方案适用于某人。

就我而言,错误是我在代码中生成实体 ID/主键,而实体框架希望在数据库端额外生成值。它有助于指定创建实体时生成的值,更具体地说,缺少它们的生成。

注释:

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }

流利的 API:

e.HasKey(e => e.Id);
e.Property(e => e.Id).ValueGeneratedNever();

我已经通过结合这两种方法解决了这个问题

var thisRole = _db.Roles.Where(r => r.Id.Equals(role.Id, 
StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
_db.Roles.Attach(thisRole);
thisRole.Name = role.Name;
_db.SaveChanges();

如果表上有触发器而不是插入,则数据库将取消操作,实体框架将触发此错误。

这里同样的错误,最终的问题是我在重用代码进行更新时插入了......

  Role role = new Role();
  role.Value = input;
  context.Add(role);                    
  await context.SaveChangesAsync();

插入时没有要更改的状态...

在mysql中,它需要一个AUTO_INCREMENT ID

喜欢这个:

更改表wfdbcore201testwftransitioninstance 修改列 ID INT(11( 不先为空AUTO_INCREMENT;

使用Razor Pages,没有一个被接受的答案对我有用。我最终使用输入模型输入来修复它。(我有自己的角色类(。

    public class AppRole:IdentityRole<int> { }
 [BindProperty]
 public InputModel Input { get; set; }
 public class InputModel
 {
     [Required]
     public string Name { get; set; }
     public string NormalizedName=>Name.ToUpper();
     public int Id { get; set; }
     // Used to check for concurrency in the post method
     public string OriginalConcurrencyStamp { get; set; }
 }

从 OnGet 方法为输入设定种子。

 public IActionResult OnGet(int? id)
 {
      if (id == null)
      {
          return NotFound();
      }
      var appRole = _context.Roles.Find(id);
      if(appRole == null) { return NotFound(); }
      Input.Id = id.Value;
      Input.Name = appRole.Name;
      Input.OriginalConcurrencyStamp = appRole.ConcurrencyStamp;
      return Page();
  }

然后在 post 方法上,我"找到"角色并在那里更新它。

 public IActionResult OnPostAsync()
 {
     if (!ModelState.IsValid)
     {
         return Page();
     }
     var appRole = _context.Roles.Find(Input.Id);
     // The role has been deleted
     if(appRole == null)
     {
         ViewData["Error"] = "This Role has been deleted by someone else.nReturn to the list.";
         return Page();
     }
     // the role has been changed by another user.
     if(appRole.ConcurrencyStamp != Input.OriginalConcurrencyStamp)
     {
         ViewData["Error"] = "This Role has been changed by someone else.nReturn to the list.";
         return Page();
     }
     // no need to SaveChanges if there are no changes.
     var hasChanges = false;
     if(appRole.Name != Input.Name)
     {
         appRole.Name = Input.Name;
         hasChanges = true;
     }
     if(appRole.SortOrder != Input.SortOrder)
     {
         appRole.SortOrder = Input.SortOrder;
         hasChanges = true;
     }
     if(appRole.NormalizedName != Input.NormalizedName)
     {
         appRole.NormalizedName = Input.NormalizedName;
         hasChanges = true;
     }
     if(!hasChanges)
     {
         ViewData["Error"] = "No Changes Detected.";
         return Page();
     }
     appRole.ConcurrencyStamp = Guid.NewGuid().ToString();
     _context.Entry(appRole).State = EntityState.Modified;
     try
     {
         _context.SaveChanges();
     }
     catch (DbUpdateConcurrencyException ex)
     {
         if (!AppRoleExists(Input.Id))
         {
             return NotFound();
         }
         else { throw; }
     }
     return RedirectToPage("./Index");
 }

我的 Edit.cshtml 文件看起来像这样。

@page
@model MyApp.Areas.Identity.Pages.Account.Manage.Roles.EditModel
@{
    ViewData["Title"] = "Edit";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}
<h1>Edit</h1>
<h4>Role</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Input.Id" />
            <input type="hidden" asp-for="Input.OriginalConcurrencyStamp" />
            <label class="text-danger">@ViewData["Error"]</label>
            <div class="form-group">
                <label asp-for="Input.Name" class="control-label"></label>
                <input asp-for="Input.Name" class="form-control" />
                <span asp-validation-for="Input.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

就我而言,问题是我试图从表中删除一个值,而不检查该值是否真的存在:


public DbSet<AdminSettings> AdminSettingsTable { get; set; }
public AdminSettings AdminSettings
  {
    get
    {
      return AdminSettingsTable.FirstOrDefault();
    }
    set
    {
      // This if-statement prevents the crash
      if (AdminSettingsTable.Contains(value))
      {
        AdminSettingsTable.Remove(value);
      }
      SaveChanges();
      AdminSettingsTable.Add(value);
   }
}

我在操作中遇到此错误delete

就我而言,我试图删除一行以及其他表中与之相关的行(外键关系(。事实证明,在数据库中添加了一个触发器,以便在删除父行时删除相关行。在删除的情况下,请确保您尝试删除的行存在,或者是否添加了任何触发器来删除相关行。

有时Timestamp (Sql Server( 字段可能会导致此问题。如果不需要,从模型类中删除它们可能会解决问题。

一个非基于查询的解决方案:就我而言,双击我的 .Net Core 应用程序中的按钮导致了 cuncurrency,因此我添加了一个代码来单击按钮的方法,使其空闲 100 毫秒。当我使用 DevExpress 时,我添加了以下代码:

    function ButtonClick(e) {  
        setTimeout(function () {  
            e.SetEnabled(false);  
        }, 100);  
// the rest of the code
    } 

您可能会找到其他解决方案来防止在其他任何地方双击。

在修复未初始化的不可空引用的新警告时,我遇到了此错误。

CS8618 - 不可为空的变量必须包含非空值,当退出构造函数。请考虑将其声明为可为空。

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings?f1url=%3FappId%3Droslyn%26k%3Dk(CS8618(#nonnullable-reference-not-initialized

带有警告的示例类如下所示:

public class BaseContainer
{
    public int Id { get; set; }
    public List<ContainerChild> ContainerChildren { get; set; } = new List<ContainerChild>();
}
public class ContainerChild
{
    public int Id { get; set; }
    public int BaseContainerId { get; set; }
    public BaseContainer BaseContainer { get; set; }
}

然后我在ContainerChild中解决了这样的警告:

public BaseContainer BaseContainer { get; set; } = new BaseContainer();

但是,当我随后向BaseContainer添加ContainerChild时,我创建了一个新BaseContainer

var baseContainer = _dbContext.BaseContainer.SingleOrDefault(x => x.Id == id);
var containerChild = new ContainerChild();
baseContainer.ContainerChildren.Add(containerChild);

通过在ContainerChild中将成员设置为可为空的引用类型BaseContainer修复,如下所示:

public BaseContainer? BaseContainer { get; set; }

对于 SQL Server,EF Core 在 Where 子句中包含一个时间戳属性,我仍然不明白为什么。因此,我必须跟踪主键和时间戳以重新附加实体。

TL;DR:检查(EF Core(生成的 SQL 中是否缺少更新语句的 Where 子句。

例:

实体:

CREATE TABLE [dbo].[Blog]
    [BlogID] [int] IDENTITY(0,1) NOT NULL,
    [sysTS] [timestamp] NOT NULL,
    [IsPublished] [bit] NOT NULL
public class Blog {
  public int BlogId { get; set; }   
  public byte[] SysTS { get; set; }
  public bool IsPublished { get; set; }
}

法典:

var blog = new Blog { IsPublished = false };
using(var context = CreateNewContext()) {
  context.Blog.Add(blog);
  await context.SaveChangesAsync();
}
var blogId = blog.BlogID
// var blogSysTs = blog.SysTs; // this I had to track too
// ... do other stuff
// here also SysTS = blogSysTs was needed
var blog2 = new Blog { BlogID = blogId, IsPublished = true }; 
using(var context2 = CreateNewContext()) {
  var entry = context2.Blog.Attach(blog2);
  entry.Property(b => b.IsPublished).IsModified = true;
  await context2.SaveChangesAsync();
}

结果如下:

UPDATE Blog
SET IsPublished = @p1
WHERE BlogID = @p2 and SysTS IS NULL

只有跟踪 SysTS 才有一个合法的更新查询:

UPDATE Blog
SET IsPublished = @p1
WHERE BlogID = @p2 and SysTS = @p3

我知道这肯定与SQL Server设置有关,我只是不知道为什么。但它的要点是:检查 EF Core 生成的 SQL 查询,了解该行未更新的原因。

相关内容

  • 没有找到相关文章

最新更新