我正在使用带有 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
喜欢这个:
更改表wfdbcore201test
。wftransitioninstance
修改列 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 查询,了解该行未更新的原因。