AutoIncrement в Entity Framework

Использовал раньше для работы с БД sql команды. Потихоньку перехожу на EF.
Раньше, я просто пропускал поле id при добавлении строки и БД сама создавала следующий id. Теперь, поле id оставлять пустым нельзя, а если использовать [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)], то id генерируется какой-то рандомный.
Во первых, не попадет ли он таким образом в уже существующий, во вторых, я хочу чтобы мои id продолжали идти по порядку, так, как это происходит с sql командами. Как это сделать?

И сразу добавлю еще вопрос. После добавления новой строки, мне нужно сразу получить ее id, раньше для этого я отправлял sql запрос с max(id), но это костыль, как это сделать по человечески?

var bill = new Bill();
            
bill.user_id = Program.f1.user_id.ToString();
bill.shop_id = Program.f1.shop_id.ToString();
bill.client_id = client.id;
bill.total = sumToPay.ToString();
bill.discount = discount.ToString();
bill.date = MySql.DateTimeNow();

billContext.bills.Add(bill);
billContext.SaveChanges();
internal class Bill
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public string id { get; set; }
    public string user_id { get; set; }
    public string shop_id { get; set; }
    public string client_id { get; set; }
    public string total { get; set; }
    public string discount { get; set; }
    public string date { get; set; }
}
CREATE TABLE `bills` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `shop_id` int(11) DEFAULT NULL,
  `client_id` int(11) DEFAULT NULL,
  `total` float DEFAULT NULL,
  `discount` float DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)

введите сюда описание изображения

UPDATE

Создаю класс для таблицы Ремонтов. В данной таблице есть много связей, и по связям как раз и вопрос. Конечно, перед тем как задавать вопрос, пытался его найти в гугле, но там описывают кейс создания связей используя CodeFirst. У меня же используются КривыеРукиFirst, так что ответ найти не вышло. Имеем:

public class Remont
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Column("id")]
        public int Id { get; set; }

        [Column("name")]
        public string Name { get; set; }

        [Column("status")]
        public string Status { get; set; }

        [Column("client_id")]
        public int ClientId { get; set; }

        [Column("details")]
        public string Details { get; set; }

        [Column("user_id")]
        public int UserId { get; set; }

        [Column("price_sum")]
        public decimal PriceSum { get; set; }

        [Column("date_added")]
        public DateTime Created { get; set; }

        [Column("date_mod")]
        public DateTime Updated { get; set; }

        [Column("shop_id")]
        public int ShopId { get; set; }

        [Column("user_id_master")]
        public int MasterId { get; set; }

        [Column("deleted")]
        public int Deleted { get; set; }



        ClientContext clientContext = new ClientContext();
        public Client Client
        {
            get { return clientContext.clients.Where(n => n.id == ClientId).First(); }
            set { ClientId = Client.id; }
        }

        //public List<Remont_> RemontTovars { get; set; }

        //[ForeignKey("user_id")]
        //public User User { get; set; }

        //[ForeignKey("shop_id")]
        //public Shop Shop { get; set; }

        //[ForeignKey("user_id_master")]
        // User Master { get; set; }
    }

Проблема следующая, есть поле Client, которое должно содержать клиента, с id = ClientId. Как я понял, в CodeFirst эта связь создалась бы автоматом. Я попытался выйти из ситуации костылем

get { return clientContext.clients.Where(n => n.id == ClientId).First(); }
            set { ClientId = Client.id; }

Но это тоже не работает. Как это организовать по-человечески?

P.S. Закомментированные поля, так же нужно реализовать, думаю, у меня это выйдет по примеру с Клиентом. Возможно, вопросы будут только к списку.

P.P.S. Русский мною никогда не изучался и грамматика на интуитивном уровне. Так что не обессудьте.


Ответы (1 шт):

Автор решения: Alexander Petrov

Раньше, я просто пропускал поле id при добавлении строки и БД сама создавала следующий id. Теперь, поле id оставлять пустым нельзя.

Свойство id можно оставлять пустым, если явно указано, что значение для него будет генерироваться в базе данных.


После добавления новой строки, мне нужно сразу получить ее id

var bill = new Bill();
// ...
billContext.bills.Add(bill);
billContext.SaveChanges();

Console.WriteLine(bill.id); // здесь будет значение из БД

При выполнении этого кода провайдер EF Core сгенерирует SQL-запрос INSERT с последующим SELECT для получения ID.
После выполнения строки billContext.SaveChanges(); экземпляр bill обретёт значение ID, полученное из базы.
Это при заданном атрибуте DatabaseGenerated(DatabaseGeneratedOption.Identity).


раньше для этого я отправлял sql запрос с max(id), но это костыль, как это сделать по человечески?

Да, это костыль. В MySql для получения ID последней вставленной строки используется LAST_INSERT_ID().


id генерируется какой-то рандомный

Значение генерируется в БД. БД ничего не знает, кто и как к ней обращается. Неважно, будет это SQL-запрос с помощью чистого ADO.NET, через micro-ORM наподобие Dapper, или с помощью LINQ-ORM типа Entity Framework. БД просто выдаст очередное значение - всё!

Почему на скриншоте такое большое различие между айдишниками? Скорее всего в промежутке между вставками этих строк были другие неудачные вставки, от которых отказались. Или строки были просто удалены.

Также нельзя исключать, что есть какие-то особенности СУБД MySql. Я не являюсь специалистом по ней. Но это сильно вряд ли.

А главное, вас не должны интересовать эти айдишники. Когда вы создаёте объект в C# (C++, Java, любом другом языке), вас же не заботит, по какому адресу в памяти он будет размещён? Точно так же не должно заботить, какой номер назначен строке в таблице БД.
Оставьте компьютеру ковыряться с циферками, он для этого предназначен. Человек должен заниматься творчеством!


Пройдёмся теперь по другим ошибкам.
В C# для именования свойств принят стиль PascalCase. Следовательно, класс должен выглядеть так:

internal class Bill
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("id")]
    public int Id { get; set; }
    [Column("user_id")]
    public int UserId { get; set; }
    [Column("shop_id")]
    public int ShopId { get; set; }
    [Column("client_id")]
    public int ClientId { get; set; }
    [Column("total")]
    public decimal Total { get; set; }
    [Column("discount")]
    public decimal Discount { get; set; }
    [Column("date")]
    public DateTime Date { get; set; }
}

В атрибуте Column задан маппинг для колонок таблицы в БД.

В комментариях вы пишете:

Как я понимаю, бд все равно возвращает все значения в виде строк.

Это не так.
Вон же у вас в CREATE TABLE явно заданы типы колонкам: int(11), float, datetime. Неужто эти типы затем не используются?

Ещё замечание. Судя по названиям: total и discount - в этих колонках хранятся денежные значения. Никогда не используйте для них вещественные типы! В БД для этого используйте decimal(15,2), а в C# ему соответствует тип decimal.


Вы сперва создали базу данных с помощью DDL. А затем вручную написали классы сущностей для Entity Framework. Такой подход называется Database First.

Можно сгенерировать классы контекста и сущностей автоматически по уже существующей БД:
Reverse Engineering
Creating a Model for an Existing Database in Entity Framework Core

→ Ссылка