Entity Framework Core不尊重标识列

6

实体框架无法正确识别我的Identity列,它坚持尝试向我的 MS SQL 数据库中的一个 Identity(自动增量)列插入一个值,这显然是一个错误,因为数据库应该提供这个值。

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();

我对此有两个问题:
  1. 我正在使用现有的数据库并生成实体类。如果我需要ValueGeneratedOnAdd(),那么为什么Scaffold-DbContext不能生成它?
  2. 即使我手动编辑生成的上下文类并添加ValueGeneratedOnAdd(),它仍然无法与相同的错误一起工作。

在其他地方,我看到建议使用UseSqlServerIdentityColumn()。但对我来说也不管用。第1点和第2点仍然适用。

非常感谢您提供的帮助。请不要建议我使用IDENTITY_INSERT,因为那样会破坏使用自动递增列的整个目的。

(我正在使用Entity Framework Core 2.2.3和Microsoft SQL Server 14)


EF Core 模型和数据库似乎是正确的。异常消息表明您的代码正在添加具有显式指定的 AssigneeId(而不是 0)的 Assignee,在这种情况下,EF Core 将尊重您的显式值(这是为了支持标识插入场景)。在调用 Add 方法之前,请确保 AssigneeId 为零。 - Ivan Stoev
1
您的标识列缺少DatabaseGeneratedOption.Identity数据注释/流畅API配置。我无法告诉您为什么脚手架没有创建它。请注意,虽然建议使用显式的[Key]注释并且应该可以工作,因为主键默认情况下是标识列,但它不是必需的。 - DevilSuichiro
IDENTITY(-1, 1) (-1) 是一个打字错误吗? - Gert Arnold
5个回答

3
这对我有效:
modelBuilder.Entity<Assignee>().Property(e => e.AssigneeId).UseIdentityColumn();

所以 UseIdentityColumn() 是关键。

我正在使用 Microsoft.EntityFrameworkCore.SqlServer v3.1.8。


1
如已在此处回答这里 - Gert Arnold

2

简短版

我们在这里得到和经历不同的结果,有些人可以重现问题,而其他人则不能。 我的经验是这取决于Id属性的值是否为0。

详细版

我的经验是,默认行为(基于名称约定)肯定有效,因此,如果您将数据库实体的属性(C#属性)命名为Id或EntityNameId,则应该可以工作。不需要任何C#实体类属性或OnModelCreating配置。 同时,如果存在问题,则无论C#实体类属性还是OnModelCreating配置都无法解决它。

...因为如果 Id属性的值不为0,生成的SQL将包含显式字段名称和值,因此我们会遇到错误。 这显然是EF Core中的一个问题,但解决方法很简单。


这证明了这是正确的答案。它特殊处理ID类型的默认值(0)。如果该值为0,则会自动生成。如果该值不为0,则会假设您已经设置了它,尝试插入并失败。我猜这不是一个问题,而是一个可疑的“功能”。 - Fizzy
在我的情况下,即使值为0也会失败,我不得不将实体配置为ValueGenerateOnAdd。之后,Id列出现了负值,我认为这是int或long类型的最小值。之后它就正常工作了。 - Krunal Parmar

2
 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 Core 依赖项: Microsoft.EntityFrameworkCore.SqlServer


0

对于数据库,首先尝试添加[key]作为数据注释

使用数据注释

[Key]
public int AssigneeId { get; set; }

流畅的API

modelBuilder.Entity<Assignee>()
        .HasKey(o => o.AssigneeId);

如果您想使用流畅的API,请点击这里这里


我正在使用脚手架从现有的数据库生成实体类。我真的不应该编辑这些类。另外,你为什么认为这会解决我的问题? - Fizzy
另外,我已经尝试过使用注释和流畅的API,但行为仍然相同。 - Fizzy
可以的,你可以使用带有MetadataTypeAttribute的部分类。例如,请参见此处:https://dev59.com/hm025IYBdhLWcg3wOzXY - fuzzybear

0

我尝试根据您的示例重现此问题,但似乎一切正常。我没有使用Scaffold,只编写了类,并尝试了您提供的模型创建代码,也没有出现问题。不过我怀疑这里可能还有更多的问题,因为仅有“Assignee”类时,EF约定会期望一个“Assignees”表,所以我认为可能还设置了更多的映射。

测试使用的是EF Core 2.0.3和2.2.4

数据库:使用了原始帖子中的脚本。

实体:

[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注释而我的不需要。我明白你所说的“额外配置”,但我不知道那会是什么/在哪里。据我所知,脚手架将所有生成的代码转储到您选择的文件夹中,我只看到我的上下文和Assignee类。 - Fizzy
你能放上DbContext的定义吗?包括构造函数、OnModelCreating等等。有没有任何类继承了IEntityTypeConfiguration?EF目前有四种配置实体的方式:特性、惯例(自动)、IEntityTypeConfiguration和通过OnModelCreating中的modelBuilder,这些方式在任何情况下都可以混合使用。你的代码似乎解决了表名与默认惯例之间的关系而不需要特性,这可能是其他行为的一个好线索。 - Steve Py
我使用搭建的脚手架上下文和模型重新运行了测试,结果仍然符合预期。已更新答案并提供详细信息。 - Steve Py

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接