Entity Framework 核心不尊重标识列

Entity Framework Core does not respect Identity columns

Entity Framework 不尊重我的 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.'

为什么要这样做?我已将它与一个涉及一个 table 和一列的架构配对:

CREATE TABLE [dbo].[Assignee](
  [AssigneeID] INT IDENTITY(-1, 1) NOT NULL
CONSTRAINT [Assignee$PrimaryKey] PRIMARY KEY CLUSTERED 
( [AssigneeID] ASC ))

将此架构发布到我的本地数据库后,我使用 Scaffold-DbContext 生成实体和上下文 classes。生成的 Assignee class 仅包含此 public 属性.

public int AssigneeId { get; set; }

此处上下文仅指Assignee

modelBuilder.Entity<Assignee>(entity =>
{
  entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});

四处搜索,我看到有人声称 E.F。为了尊重标识列,上下文应将 属性 配置为 ValueGeneratedOnAdd()。换句话说,上下文 class 中的行应为:

entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID")
  .ValueGeneratedOnAdd();

我有两个问题:

  1. 我从现有数据库开始并生成实体 classes。如果我需要 ValueGeneratedOnAdd() 那么为什么 Scaffold-DbContext 没有生成它?
  2. 即使我手动编辑生成的上下文 class 并添加 ValueGeneratedOnAdd() 仍然 不会出现相同的错误。

我在其他地方看到了使用 UseSqlServerIdentityColumn() 的建议。这对我也不起作用。第 1 点和第 2 点仍然适用。

如有任何帮助,我们将不胜感激。请不要建议我使用 IDENTITY_INSERT,因为这违背了使用自动增量列的全部意义。

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

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

有数据标注

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

流利API

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

想要流畅使用请看here or hereAPI

我已尝试根据您的示例重现此问题,但它似乎工作正常。不过我没有使用脚手架,只是编码了 class 并且我尝试了您拥有的模型创建代码并且它没有问题。我怀疑还需要更多,因为只有 "Assignee" class,EF 约定需要 "Assignees" table,所以我怀疑设置了更多映射向上。

使用 EF Core 2.0.3 和 2.2.4 测试

DB:使用了 OP 的脚本。

实体:

[Table("Assignee")]
public class Assignee
{
    public int AssigneeId { get; set; }
}

我必须使用 Table 属性映射到 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 覆盖中不需要此列的任何内容。

我怀疑有一些额外的配置潜伏在某处,因为没有提到 table 名称问题,无论是手动添加还是通过脚手架添加,这都会搞砸 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; }
}

我确实弄清楚了为什么需要我的 table 注释。 EF Core(不确定是否也适用于 EF6)将 table 名称的约定基于 DbContext 中的 DbSet 变量名称。除了 DbSet 名称外,我看不出脚手架生成的上下文和我自己的上下文有任何配置差异。我将原来的 DbContext 的 DbSet 名称重命名为 "Assignee" 并且它在没有 Table 属性的情况下工作。

也就是说,根据提供的信息,您的代码应该可以工作。某些细节隐藏在细节中,因为这个示例确实有效,因此您需要提供有关在您的情况下绝对不起作用的示例的更多详细信息。

 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

短版

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

详细版

根据我的经验,默认行为(基于名称约定)肯定有效,因此如果您将数据库实体的属性 (C# 属性) 命名为 Id 或 EntityNameId,它应该有效。没有 C# 实体 class 属性,既不需要 OnModelCreating 配置。 同时,如果问题不存在,则 No C# entity class 属性和 OnModelCreating 配置都无法修复它。

...因为如果Id属性的值不是0,生成的SQL 将包含明确的字段名称和值,所以我们得到了错误。 这显然是 EF 核心中的问题,但解决方法很简单..

这对我有用:

modelBuilder.Entity<Assignee>().Property(e => e.AssigneeId).UseIdentityColumn();

所以UseIdentityColumn()是关键。

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