Связи EntityFrameworkCore DataBaseFirst MySql
Пытаюсь настроить связи в EF. Сначала была создана БД, потом прописаны классы EF. Имеем две таблицы:
REATE TABLE `remonts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`status` varchar(255) DEFAULT NULL,
`client_id` int(11) DEFAULT NULL,
`details` varchar(255) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`price_sum` float DEFAULT NULL,
`date_added` datetime DEFAULT NULL,
`date_mod` datetime DEFAULT NULL,
`shop_id` int(11) DEFAULT NULL,
`user_id_master` int(11) DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `client_id` (`client_id`),
CONSTRAINT `remonts_ibfk_1` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=266 DEFAULT CHARSET=utf8;
CREATE TABLE `clients` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`telephone` varchar(255) DEFAULT NULL,
`bike` varchar(255) DEFAULT NULL,
`details` varchar(255) DEFAULT NULL,
`balance` float DEFAULT '0',
`discount` varchar(255) NOT NULL DEFAULT '0',
`discount_type` varchar(255) NOT NULL DEFAULT 'Static',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `id_2` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=215 DEFAULT CHARSET=utf8;
Так же имеем два класса
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; }
[ForeignKey("client_id")]
public Client Client { get; set; }
//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; }
}
public class Client
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("id")]
public int id { get; set; }
public string name { get; set; } = "Розничный покупатель";
public string telephone { get; set; } = String.Empty;
public string bike { get; set; } = String.Empty;
public string details { get; set; } = String.Empty;
public string balance { get; set; } = "0";
public string discount { get; set; } = "0";
public string discount_type { get; set; } = "Static";
}
Да, класс клиент создавался раньше, и в нем нормально не прописаны типы данных, но этот класс уже юзается, так что поправлю позже.
Так же имеем тестовый код, которым я пытаюсь проверить работоспособность навигационного свойства.
private void Form1_DoubleClick(object sender, EventArgs e)
{
MessageBox.Show(remontContext.remonts.First().Client.name);
}
Да, я вижу что он не может найти колонку client_id1. Я использовал поиск во всему проекту и нет, я не знаю где он взял эту единицу в конце. Если закоментить [ForeignKey("client_id")], то EF просто не понимает что это навигационное свойство и оставляет его пустым. Связь в базе данных прописана (вроде, делал первый раз, но все как показывали умные люди).
Добавляю классы контекста
public class RemontContext : DbContext
{
public DbSet<Remont> remonts { get; set; }
public RemontContext() { }
protected override void OnConfiguring(DbContextOptionsBuilder ob)
{
ob.UseMySQL(MySql.mySqlConnection);
}
}
public class ClientContext : DbContext
{
public DbSet<Client> clients { get; set; }
public ClientContext() { }
protected override void OnConfiguring(DbContextOptionsBuilder ob)
{
ob.UseMySQL(MySql.mySqlConnection);
}
}
UPDATE
Изменил [ForeignKey("client_id")] на [ForeignKey("id")]
Имеем ошибку:

я абсолютно точно нигде не использовал Remont_id. Возможно, этой ошибкой EF пытается получить доступ к таблице Remont и столбцу id , которого соответственно, как и таблицы, нету.
Ответы (1 шт):
Для начала скажу, что я не являюсь специалистом по MySql, но всё же могу отметить наличие ошибок в DDL-скриптах.
В частности, для хранения явно денежных величин задан тип float, хотя нужно decimal.
Мне непонятно наличие UNIQUE KEY `id` (`id`) и UNIQUE KEY `id_2` (`id`).
Оставим это на совести автора.
Переходим к коду C#.
Если уж вы взялись вручную создавать код сущностей, то вооружитесь таблицей маппинга типов MySql в типы .NET.
| MySql | .NET |
|---|---|
| bool, boolean, bit(1) | System.Boolean |
| tinyint | System.SByte |
| tinyint unsigned | System.Byte |
| smallint, year | System.Int16 |
| int, integer, smallint unsigned, mediumint | System.Int32 |
| bigint, int unsigned, integer unsigned, bit | System.Int64 |
| float | System.Single |
| double, real | System.Double |
| decimal, numeric, dec, fixed, bigint unsigned, float unsigned, double unsigned, serial | System.Decimal |
| date, timestamp, datetime | System.DateTime |
| datetimeoffset | System.DateTimeOffset |
| time | System.TimeSpan |
| char, varchar, tinytext, text, mediumtext, longtext, set, enum, nchar, national char, nvarchar, national varchar, character varying | System.String |
| binary, varbinary, tinyblob, blob, mediumblob, longblob, char byte | System.Byte[] |
| geometry | System.Data.Spatial.DbGeometry |
Не создавайте отдельные классы контекста для каждой таблицы. В одном контексте нужно создать несколько свойств DbSet.
public class MyContext : DbContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Remont> Remonts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder ob)
{
...
}
}
Причём соблюдайте общепринятый нейминг: PascalCase для свойств.
Теперь разберёмся, откуда в исключении появлялась колонка r.client_id1.
В классе Remont на свойство Client вы навесили атрибут [ForeignKey("client_id")], что и является причиной ошибки.
Этот атрибут должен указывать на свойство класса, а не на колонку в таблице БД. А свойство у нас называется ClientId. Т. к. провайдер БД не может найти свойство с именем client_id, он создаёт теневое свойство с таким именем. И при явном указании атрибута внешнего ключа с совпадающим именем к нему добавляется суффикс в виде 1 - это и образует имя client_id1.
Проще всего удалить атрибут [ForeignKey("client_id")]. Провайдер сам разберётся в связях.
То есть в коде должно остаться:
[Column("client_id")]
public int ClientId { get; set; }
public Client Client { get; set; }
Далее нужно освоить загрузку связанных данных. Чаще всего используется Eager loading - энергичная загрузка. Нужно добавить к запросу Include.
context.Remonts.Include(r => r.Client).First().Client
Это загрузит одновременно данные связанных сущностей Remont и Client.
Чтобы не описывать вручную сущности C#, используйте scaffolding.
Возможно, в начале будет проще освоить подход Code First.
