Entity Framework Core does not respect Identity columns

时间:2019-04-16 22:08:21

标签: c# entity-framework tsql entity-framework-core auto-increment

Entity Framework is not respecting my Identity columns. It insists on trying to insert a value into an Identity (auto-increment) column in my MS SQL DB, which is obviously an error since the DB is supposed to supply the value.

System.Data.SqlClient.SqlException: 'Cannot insert explicit value for identity column in table 'Assignee' when IDENTITY_INSERT is set to OFF.'

Why is it trying to do that? I've paired it down to a schema involving one table and one column:

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

After publishing this schema to my local DB I use Scaffold-DbContext to generate entity and context classes. The generated Assignee class contains just this public property.

public int AssigneeId { get; set; }

The context only refers to Assignee here:

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

Searching around I see people claiming that for E.F. to respect Identity columns, the context should configure the property with ValueGeneratedOnAdd(). In other words, the line in the context class should read:

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

I have two problems with this:

  1. I'm starting with an existing DB and generating entity classes. If I need ValueGeneratedOnAdd() then why isn't Scaffold-DbContext generating it?
  2. Even if I manually edit the generated context class and add ValueGeneratedOnAdd() it still doesn't work with the same error.

Elsewhere I see suggestions to use UseSqlServerIdentityColumn(). That also doesn't work for me. Points 1 and 2 still apply.

Any help would be greatly appreciate. Please don't suggest that I use IDENTITY_INSERT as that defeats the entire point of using auto-increment columns.

(I am using Entity Framework Core 2.2.3 and Microsoft SQL Server 14)

5 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

简短版本

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

详细版本

根据我的经验,默认行为(基于名称约定)肯定可以正常工作,因此,如果要将数据库实体的属性(C#属性)命名为Id或EntityNameId,它应该可以工作。没有C#实体类属性,也不需要OnModelCreating配置。 如果问题同时存在,则没有C#实体类属性,也没有OnModelCreating配置将其解决。

... 由于如果 Id属性的值不为0 ,则生成的SQL将包含显式字段名称和值,因此出现了错误。 显然,这是EF核心中的问题,但解决方法很容易。

答案 2 :(得分:1)

这对我有用:

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

所以UseIdentityColumn()是关键。

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

答案 3 :(得分:0)

For DB first try adding [key] as a data annotation

With Data annotation

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

fluent API

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

See here or here if you want to use fluent API

答案 4 :(得分:0)

I've tried to reproduce this issue based on your example but it appears to work just fine. I did not use Scaffold though, just coded class and I tried the model creating code you had and it hasn't had an issue. I suspect there has to be more to this though because with just the "Assignee" class, EF convention is expecting an "Assignees" table, so I suspect there is more mapping being set up.

Tested with EF Core 2.0.3 and 2.2.4

DB: used the OP's script.

Entity:

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

I had to use the Table attribute to map to the table name.

Context:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

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

as-per OP comment.

Test:

   [Test]
    public void TestIncrement()
    {
        using (var context = new TestDbContext())
        {
            var newItem = new Assignee();
            context.Assignees.Add(newItem);
            context.SaveChanges();
        }
    }

Works as expected.

However, what I'd normally have for the entity:

[Table("Assignee")]
public class Assignee
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("AssigneeID")]
    public int AssigneeId { get; set; }
}

And then nothing for this column needed in the context OnModelCreating override.

I suspect that there is some additional configuration lurking somewhere given there is no mention of the table name issue, either manually added or via scaffold that is goofing up EF. I was full-on expecting EF to fail without the Key/DbGenerated attributes, but it seemed to work just fine.

Edit: Also tried this with scafolding running Scaffold-DbContext across the existing schema. Again, worked without an issue. For comparison against your tests:

Generated DbContext: (Unaltered save removing the warning and connection string details.)

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");
        });
    }
}

Generated Entity: (Unaltered)

public partial class Assignee
{
    public int AssigneeId { get; set; }
}

I did figure out why my table annotation was needed. EF Core (Not sure if applies to EF6 as well) was basing the convention for the table name on the DbSet variable name in the DbContext. I couldn't see any config difference with the scaffold generated context and my own, except the DbSet name. I renamed my original DbContext's DbSet name to "Assignee" and it worked without the Table attribute.

That said, based on the information present your code should work. Something is lurking in the details because this example does work so you will need to provide more detail about an example that definitely doesn't work in your case.