Entity Framework Core не учитывает столбцы Identity

Entity Framework не учитывает мои столбцы Identity. Он настаивает на попытке вставить значение в столбец Identity (автоинкремент) в моей БД MS SQL, что, очевидно, является ошибкой, поскольку БД должна предоставлять значение.

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

Поискав вокруг, я вижу людей, утверждающих, что для того, чтобы EF учитывал столбцы Identity, контекст должен настроить свойство с помощью 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 кажутся правильными. Сообщение об исключении указывает, что ваш код добавляет Assignee с явно указанным AssigneeId (кроме 0), и в этом случае EF Core учитывает ваше явное значение (это необходимо для поддержки сценариев вставки удостоверений). Перед вызовом метода AssigneeId убедитесь, что Add равно нулю.

Ivan Stoev 17.04.2019 04:28

В столбце идентификаторов отсутствует аннотация данных DatabaseGeneratedOption.Identity/конфигурация API Fluent. Почему это не было создано эшафотом, я не могу сказать. Обратите внимание, что предложенная явная аннотация [Key] не является необходимой, хотя она должна работать, поскольку PK по умолчанию являются столбцами идентификаторов.

DevilSuichiro 17.04.2019 10:34

IDENTITY(-1, 1) (-1) опечатка?

Gert Arnold 23.05.2020 22:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
3
8 669
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Для БД сначала попробуйте добавить [ключ] в качестве аннотации к данным.

С аннотацией данных

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

свободный API

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

См. здесь или здесь, если вы хотите использовать свободный API.

Я создаю свои классы сущностей из существующей БД, используя скаффолд. Я действительно не должен редактировать эти классы. Кроме того, почему вы ожидаете, что это решит мою проблему?

Fizzy 17.04.2019 01:19

Кроме того, я пробовал это как с аннотацией, так и с плавным API, и поведение осталось прежним.

Fizzy 17.04.2019 01:27

Да, вы можете использовать частичные классы с MetadataTypeAttribute, см. здесь, например, stackoverflow.com/questions/6131754/…

fuzzybear 17.04.2019 19:17

Я попытался воспроизвести эту проблему на основе вашего примера, но, похоже, он работает нормально. Однако я не использовал Scaffold, просто закодировал класс, и я попробовал модель, создающую код, который у вас был, и у него не было проблем. Я подозреваю, что в этом должно быть что-то большее, потому что только с классом «Уполномоченный» соглашение EF ожидает таблицу «Уполномоченные», поэтому я подозреваю, что настраивается больше сопоставления.

Протестировано с 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");
        });
    }

согласно комментарию ОП.

Тестовое задание:

   [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) основывал соглашение об имени таблицы на имени переменной DbSet в DbContext. Я не видел никаких различий в конфигурации с контекстом, сгенерированным скаффолдом, и моим собственным, кроме имени DbSet. Я переименовал свое оригинальное имя DbSet DbContext в «Assignee», и оно работало без атрибута Table.

Тем не менее, на основе представленной информации ваш код должен работать. Что-то скрывается в деталях, потому что этот пример действительно работает, поэтому вам нужно будет предоставить более подробную информацию о примере, который определенно не работает в вашем случае.

Это довольно подробный пример. К сожалению, у меня есть большая существующая БД, и мне приходится использовать скаффолд. :-( Очень странно, что вашему коду нужна аннотация таблицы, а моему нет. Я понимаю, что вы имеете в виду под «дополнительной конфигурацией», но я не знаю, что/где это будет. Скаффолд AFIK сбрасывает весь сгенерированный код в папку по вашему выбору, и единственное, что я вижу, это мой контекст и классы Assignee.

Fizzy 17.04.2019 01:25

Можете ли вы разместить определение DbContext? Constructor, OnModelCreating и т. д. Существуют ли какие-либо классы, расширяющие IEntityTypeConfiguration? EF (по-прежнему) имеет 4 способа настройки сущностей: атрибуты, соглашение (автоматически), IEntityTypeConfiguration и через modelBuilder в OnModelCreating, и любое их сочетание может использоваться в любом заданном сценарии. То, что ваше, кажется, разрешает имя таблицы по сравнению с соглашением по умолчанию без атрибута, может быть хорошим ключом к другому поведению.

Steve Py 17.04.2019 02:46

Я повторно запустил тест, используя контекст и модель, построенные на каркасе, и он все еще работал, как и ожидалось. Обновил ответ с подробностями.

Steve Py 17.04.2019 03:17
 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 или нет.

Подробная версия

По моему опыту, поведение по умолчанию (основанное на соглашении об именах) определенно работает, поэтому, если вы называете атрибут вашего объекта db (свойство C#) идентификатором или EntityNameId, он должен работать. Нет атрибутов класса сущностей C#, ни конфигурация OnModelCreating не требуется. В то же время, если проблема существует, ни атрибуты класса сущностей C#, ни конфигурация OnModelCreating не исправят ее.

...поскольку если значение свойства Id не равно 0, сгенерированный SQL будет содержать явное имя и значение поля, поэтому мы получили ошибку. Это явно проблема в ядре EF, но обходной путь прост.

Это оказывается правильным ответом. Он специально обрабатывает значение по умолчанию для типа идентификатора (0). Если значение равно 0, оно будет сгенерировано. Если значение не равно 0, предполагается, что вы его установили, попытаетесь вставить и потерпите неудачу. Я бы предположил, что это не проблема, а сомнительная "фича".

Fizzy 06.10.2020 17:46

Это работает для меня:

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

Итак, UseIdentityColumn() является ключом.

Я использую Microsoft.EntityFrameworkCore.SqlServer v3.1.8.

Как уже ответил здесь.

Gert Arnold 05.10.2020 09:01

Другие вопросы по теме