Entity Framework Core 不尊重标识列
Posted
技术标签:
【中文标题】Entity Framework Core 不尊重标识列【英文标题】:Entity Framework Core does not respect Identity columns 【发布时间】:2019-09-07 02:13:49 【问题描述】:Entity Framework 不尊重我的 Identity
列。它坚持尝试在我的 MS SQL DB 中的 Identity
(自动增量)列中插入一个值,这显然是一个错误,因为 DB 应该提供该值。
System.Data.SqlClient.SqlException: 'Cannot insert explicit value for identity column in table 'Assignee' when IDENTITY_INSERT is set to OFF.'
为什么要这样做?我已将其与涉及一个表和一列的架构配对:
CREATE TABLE [dbo].[Assignee](
[AssigneeID] INT IDENTITY(-1, 1) NOT NULL
CONSTRAINT [Assignee$PrimaryKey] PRIMARY KEY CLUSTERED
( [AssigneeID] ASC ))
将此架构发布到我的本地数据库后,我使用Scaffold-DbContext
生成实体和上下文类。生成的Assignee
类只包含这个公共属性。
public int AssigneeId get; set;
这里的上下文仅指Assignee
:
modelBuilder.Entity<Assignee>(entity =>
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
);
四处搜索我看到有人声称 E.F. 尊重身份列,上下文应该使用ValueGeneratedOnAdd()
配置属性。换句话说,上下文类中的行应该是:
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID")
.ValueGeneratedOnAdd();
我有两个问题:
-
我从现有数据库开始并生成实体类。如果我需要
ValueGeneratedOnAdd()
,那为什么不是Scaffold-DbContext
生成它?
即使我手动编辑生成的上下文类并添加ValueGeneratedOnAdd()
,它仍然不会出现相同的错误。
在其他地方,我看到了使用 UseSqlServerIdentityColumn()
的建议。这对我也不起作用。第 1 点和第 2 点仍然适用。
任何帮助将不胜感激。请不要建议我使用IDENTITY_INSERT
,因为这会破坏使用自动增量列的全部意义。
(我使用的是 Entity Framework Core 2.2.3 和 Microsoft SQL Server 14)
【问题讨论】:
EF Core 模型和数据库似乎是正确的。异常消息表明您的代码正在添加Assignee
并明确指定 AssigneeId
(0
除外),在这种情况下,EF Core 尊重您的明确值(这是为了支持身份插入方案)。在调用Add
方法之前确保AssigneeId
为零。
您的身份列中缺少 DatabaseGeneratedOption.Identity 数据注释/流利的 API 配置。为什么它不是由脚手架创建的,我不知道。请注意,建议的显式 [Key] 注释不是必需的,尽管它应该可以工作,因为 PK 默认是标识列。
IDENTITY(-1, 1)
(-1) 是错字吗?
【参考方案1】:
protected override void OnModelCreating(ModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Todo>(entity =>
entity.Property(x => x.Id)
.HasColumnName("id")
.HasColumnType("int")
.ValueGeneratedOnAdd()
**.UseIdentityColumn();**
尝试这样做。 Ef 核心依赖:Microsoft.EntityFrameworkCore.SqlServer
【讨论】:
【参考方案2】:这对我有用:
modelBuilder.Entity<Assignee>().Property(e => e.AssigneeId).UseIdentityColumn();
所以UseIdentityColumn()
是关键。
我正在使用 Microsoft.EntityFrameworkCore.SqlServer v3.1.8。
【讨论】:
正如已经回答的here。【参考方案3】:短版
我们在这里得到和体验不同的结果,一个可以重现该问题,而其他人则不能。 我的经验取决于 Id 属性的值是否为 0。
详细版
我的经验是,默认行为(基于名称约定)肯定有效,因此如果您将数据库实体的属性(C# 属性)命名为 Id 或 EntityNameId,它应该可以工作。没有 C# 实体类属性,也不需要 OnModelCreating 配置。 同时,如果问题不存在,则没有 C# 实体类属性,OnModelCreating 配置也不会修复它。
...因为如果Id属性的值不是0,生成的SQL会包含显式的字段名和值,所以报错。 这显然是 EF 核心中的问题,但解决方法很简单..
【讨论】:
事实证明这是正确的答案。它特别对待 ID 类型 (0) 的默认值。如果值为 0,那么它将生成它。如果该值不为 0,那么它将假定您已设置它,尝试插入它并失败。我猜这不是问题,而是一个可疑的“功能”。【参考方案4】:对于 DB,首先尝试添加 [key] 作为数据注释
带数据注释
[Key]
public int AssigneeId get; set;
流畅的 API
modelBuilder.Entity<Assignee>()
.HasKey(o => o.AssigneeId);
如果您想使用流畅的 API,请参阅 here 或 here
【讨论】:
我正在使用脚手架从现有数据库生成实体类。我真的不应该编辑这些课程。另外,您为什么认为这会解决我的问题? 另外,我已经用注释和流畅的 API 尝试了这个,行为仍然是一样的。 是的,你可以,使用带有 MetadataTypeAttribute 的部分类,例如 ***.com/questions/6131754/…【参考方案5】:我已尝试根据您的示例重现此问题,但它似乎工作得很好。不过,我没有使用 Scaffold,只是编写了类,我尝试了您拥有的模型创建代码,但没有出现问题。我怀疑这必须更多,因为只有“Assignee”类,EF 约定需要一个“Assignees”表,所以我怀疑设置了更多映射。
使用 EF Core 2.0.3 和 2.2.4 测试
DB:使用了 OP 的脚本。
实体:
[Table("Assignee")]
public class Assignee
public int AssigneeId get; set;
我必须使用 Table 属性来映射到表名。
上下文:
protected override void OnModelCreating(ModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Assignee>(entity =>
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
);
根据 OP 评论。
测试:
[Test]
public void TestIncrement()
using (var context = new TestDbContext())
var newItem = new Assignee();
context.Assignees.Add(newItem);
context.SaveChanges();
按预期工作。
但是,我通常拥有的实体:
[Table("Assignee")]
public class Assignee
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("AssigneeID")]
public int AssigneeId get; set;
然后在上下文 OnModelCreating 覆盖中不需要此列的任何内容。
我怀疑有一些额外的配置潜伏在某个地方,因为没有提到表名问题,无论是手动添加还是通过脚手架搞砸了 EF。我完全期待 EF 在没有 Key/DbGenerated 属性的情况下会失败,但它似乎工作得很好。
编辑:还尝试了跨现有架构运行 Scaffold-DbContext 的脚手架。再次,工作没有问题。 为了与您的测试进行比较:
生成的 DbContext:(未更改保存删除警告和连接字符串详细信息。)
public partial class AssigneeContext : DbContext
public AssigneeContext()
public AssigneeContext(DbContextOptions<AssigneeContext> options)
: base(options)
public virtual DbSet<Assignee> Assignee get; set;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
if (!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer("Data Source=machine\\DEV;Initial Catalog=Spikes;uid=user;pwd=password;MultipleActiveResultSets=True");
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
modelBuilder.Entity<Assignee>(entity =>
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
);
生成的实体:(未更改)
public partial class Assignee
public int AssigneeId get; set;
我确实弄清楚了为什么需要我的表格注释。 EF Core(不确定是否也适用于 EF6)将表名的约定基于 DbContext 中的 DbSet 变量名。除了 DbSet 名称外,我看不出与脚手架生成的上下文和我自己的上下文有任何配置差异。我将原始 DbContext 的 DbSet 名称重命名为“Assignee”,并且它在没有 Table 属性的情况下工作。
也就是说,根据提供的信息,您的代码应该可以工作。细节中潜伏着一些东西,因为这个例子确实有效,所以你需要提供一个在你的情况下绝对不起作用的例子的更多细节。
【讨论】:
这是一个非常详尽的例子。不幸的是,我有一个很大的现有数据库并且必须使用脚手架。 :-( 您的代码需要 Table 注释而我不需要,这很奇怪。我明白您所说的“附加配置”是什么意思,但我不知道那是什么/在哪里。AFIK 脚手架将所有生成的代码转储到您选择的文件夹,我唯一看到的是我的上下文和受让人类。 你能把 DbContext 的定义放上去吗?构造函数、OnModelCreating 等。是否有任何扩展 IEntityTypeConfiguration 的类? EF(仍然)有 4 种方式来配置实体,属性、约定(自动)、IEntityTypeConfiguration 和通过 OnModelCreating 中的 modelBuilder,并且可以在任何给定场景中使用这些的任何组合。你的似乎解决了表名与没有属性的默认约定可能是其他行为的一个很好的线索。 我使用脚手架构建的上下文和模型重新运行了测试,它仍然按预期工作。用详细信息更新了答案。以上是关于Entity Framework Core 不尊重标识列的主要内容,如果未能解决你的问题,请参考以下文章
Entity Framework Core 2.0:错误不包含 UseSqlServerIdentityColumn 的定义
在 Entity Framework Core 中重命名外键而不删除数据
以下方法或属性 [ Entity Framework Core ] [ OnModelCreating ] 之间的调用不明确
Entity Framework Core:外键和引用表 Id 列不匹配
在依赖注入中使用 Entity Framework Core DbContext 时不调用 OnModelCreating