Форум программистов, компьютерный форум, киберфорум
C#: Базы данных
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.85/40: Рейтинг темы: голосов - 40, средняя оценка - 4.85
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
MySQL

Как сделать оптимальный код для вставки строк (INSERT and FOREIGN KEY) (linq2db)

17.02.2021, 15:55. Показов 8535. Ответов 27

Студворк — интернет-сервис помощи студентам
Доброго всем!

Когда мелкое приложение, которое использует БД с парой таблиц, вопросов не возникает.
А когда приложение по крупнее, нужна правильно спроектированная БД.

Вот с самой проектировкой БД более-меннее понятно.

Но вопросов много возникает при вставке новых значений в таблицы с внешними ключами (FOREIGN KEY).

Все видеокурсы о SQL на 90+% состоят из уроков на SELECT.
В этих курсах или данные уже существуют, до начала урока, и делаются выборки, или добавляются данные с уже известными внешними ключами.
Но как оптимально поступать в реальных приложениях?



Это всё естественно примитивная модель БД, чтобы лучше объяснить что я хочу.
Мне нужно не создать структуру БД, а понять "правильный"|"оптимальный"|"лёгкий"|"над ёжный" способ работы с БД из .Net + linq2db и FOREIGN KEY.


Представьте вот такую ситуацию.
Есть некий сервис онлайн курсов "Online".
К нему есть ограниченный доступ по API (по 1 разу в 60 минут к Student и Course).

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

Чтобы не было многабукаф, спрячу код под спойлеры.


Сущности получаемые по API

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
namespace Online.API
{
    public class Student
    {
        /// <summary>
        /// Id Пользователя на сервисе API
        /// </summary>
        public long Id { get; set; }
        /// <summary>
        /// Имя пользователя на сервисе API
        /// </summary>
        public string FullName { get; set; }
        /// <summary>
        /// Количество уже затраченных часов обучения на всех курсах.
        /// </summary>
        public int TotalHours { get; set; }
        /// <summary>
        /// Все Id курсов в которых участвует пользователь
        /// </summary>
        public List<long> CourseIds { get; set; }
    }
 
    public class Course
    {
        /// <summary>
        /// Id конкретного курса, можно получить только через запрос к (API) Student
        /// Получить все остальные данные по Курсу можно только при наличии Id
        /// </summary>
        public long Id { get; set; }
        /// <summary>
        /// Название курса
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Сколько часов длится курс
        /// </summary>
        public int Hours { get; set; }
    }
}


Из двух API объектов Student и Course получается три таблицы в БД, так как один студент может быть записан на несколько курсов.

`StudentCourse` таблица многие ко многим.
У одного студента может быть много курсов.
У одного курса можеть быть много студентов.

SQL создания таблиц

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CREATE TABLE `Student` (
    `Id` BIGINT(20) UNSIGNED NOT NULL,
    `FullName` VARCHAR(255) NOT NULL,
    `TotalHours` INT(11) NOT NULL,
    PRIMARY KEY (`Id`)
);
 
CREATE TABLE `Course` (
    `Id` BIGINT(20) UNSIGNED NOT NULL,
    `Name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`Id`)
);
 
CREATE TABLE `StudentCourse` (
    `StudentId` BIGINT(20) UNSIGNED NOT NULL,
    `CourseId` BIGINT(20) UNSIGNED NOT NULL,
    PRIMARY KEY (`StudentId`, `CourseId`),
    CONSTRAINT `FK_studentcourse_student` FOREIGN KEY (`StudentId`) REFERENCES `Student` (`Id`),
    CONSTRAINT `FK_studentcourse_course` FOREIGN KEY (`CourseId`) REFERENCES `Course` (`Id`)
);
 
-- Так же Вопрос по SQL
-- Почему при создании таблицы `StudentCourse` этим запросом, появляется индекс
    INDEX `FK_studentcourse_course` (`CourseId`) USING BTREE,
-- Я же ограничил дублирование записей в "PRIMARY KEY (`StudentId`, `CourseId`),"


Соответственно для этих таблиц БД созданы классы-сущности в C#

Сущности C# для linq2db

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
namespace Online.Model
{
    public class Student
    {
        [PrimaryKey]
        public long Id { get; set; }
 
        [Column, NotNull]
        public string FullName { get; set; }
 
        [Column, NotNull]
        public int TotalHours { get; set; }
 
        [NotColumn]
        [Association(ThisKey = nameof(Id), OtherKey = nameof(Online.Model.StudentCourse.StudentId))]
        public List<StudentCourse> StudentCourses { get; set; }
    }
 
    public class Course
    {
        [PrimaryKey]
        public long Id { get; set; }
        [Column, Nullable]
        public string Name { get; set; }
        [Column, Nullable]
        public int Hours { get; set; }
    }
    
    public class StudentCourse
    {
        [PrimaryKey]
        public long StudentId { get; set; }
 
        [PrimaryKey]
        public long CourseId { get; set; }
 
        [NotColumn]
        [Association(ThisKey = nameof(StudentId), OtherKey = nameof(Online.Model.Student.Id))]
        public Student Student { get; set; }
 
        [NotColumn]
        [Association(ThisKey = nameof(CourseId), OtherKey = nameof(Online.Model.Course.Id))]
        public Course Course { get; set; }
    }
}



А теперь последовтельность действий.

Я могу сделать запрос и получить список пользователей (Online.API.Student -- get.Student?)
И получить объекты класса Online.API.Student.

К примеру получаю вот такого пользователя.
C#
1
2
3
4
5
6
7
Online.API.Student studentAPI = new Online.API.Student
{
    Id = 1234,
    FullName = "Иван Иванов",
    TotalHours = 67,
    CourseIds = new List<long> { 1245, 3287, 2456, 7643 },
};

Затем я конвертирую его в Online.Model.Student и добавляю в таблицу БД `Student`
C#
1
2
3
4
5
6
7
8
9
10
Online.Model.Student student = new Online.Model.Student
{
    Id = studentAPI.Id,
    FullName = studentAPI.FullName,
    TotalHours = studentAPI.TotalHours,
};
 
// ранее созданный объект БД (linq2db)
// Если существует, то обновить, так как могут быть изменения
db.InsertOrReplace(student);

И вот теперь мне нужно добавить данные в таблицу `StudentCourse`.

Но есть несколько вопросов.
Я не могу добавить с обновлением потому, что в таблице стоит PrimaryKey на оба поля.
А так же я не знаю существует ли Course.Id в таблице `Course`.

То есть мне нужно проверить существование Course.Id, затем отсутствие этой пары в `StudentCourse`.


С этого места мне кажетя всё идёт не очень гладко.
Может и до этого места тоже было не очень, но сейчас и я это вижу.


Реализация вставки.
С предварительной проверкой вставляем строки
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
foreach (var courseId in studentAPI.CourseIds)
{
    // Чекаем существование курса
    var check = db.Course
        .Where(x => x.Id == courseId)
        .Select(x => 1).FirstOrDefault();
 
    // Если не существует то добавить
    if (check != 1)
    {
        // Получаем по API всю информацию по Course.Id
        Online.Model.Course course = new Course
        {
            Id = courseId,
            Name = "Программирование Баз Данных",
            Hours = 23,
        };
 
        db.InsertOrReplace(course);
    }
}

И только теперь добавляем в таблицу, предварительно проверив на существование.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
foreach (var courseId in studentAPI.CourseIds)
{
    var check = db.StudentCource
        .Where(x => x.StudentId == studentAPI.Id)
        .Where(x => x.CourseId == courseId)
        .Select(x => 1).FirstOrDefault();
 
    if (check != 1)
    {
        var studentCourse = new StudentCourse
        {
            StudentId = studentAPI.Id,
            CourseId = courseId,
        };
 
        db.Insert(studentCourse);
    }
}

Вопрос вот собственно в чём.
При каждом обновлении Student, мне нужно совершать кучу этих запросов.

Как вообще опытные разработчики с этим поступают?
Я думаю должен быть способ это сделать проще.
Допустим сам ORM linq2db может что-то уже умеет делать это проще?



P.S. Сразу получить все курсы с API я не могу. (Online.API.Course)
Могу получать только пользоватлелей, и уже потом по CourseIds получать описание и всё остальное по самому курсу.

Буду благодарен за любую помощь.
Конструктивная критика, с указанием на ошибки, и способы их решений крайне приветствуется.
0
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
17.02.2021, 15:55
Ответы с готовыми решениями:

Как сделать Insert в таблицу, которая содержит foreign key другой таблицы
У меня такая проблемка. Есть 3 таблицы, Order, product и client. В Order хранится id-шники product и client. Я должен сделать Insert в...

SQLite - оптимальный размер транзакции, стоит ли использовать FOREIGN KEY, связь PRIMARY KEY и INDEX
1. Оптимальный размер транзакции 1.1. Есть ли какое-то ограничение на размер или содержание одной транзакции? 1.2. Может ли размер...

Insert запрос к sqlite с foreign key
Здравствуйте. есть такая тестовая база CREATE TABLE mt (id INTEGER PRIMARY KEY AUTOINCREMENT, zk INTEGER, kt TEXT); CREATE...

27
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
19.02.2021, 15:22  [ТС]
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от MsGuns Посмотреть сообщение
Это вообще не решение Это простейший пример, на котором видна связка таблиц через кросс-таблицу.
Странно.
А откуда тогда эти выводы?
Цитата Сообщение от MsGuns Посмотреть сообщение
"Моя" модель - это классика. Ваша - грабли с костылем. На 100 записях они будут работать одинаково, на 1млн Ваша будет тормозить. К тому в Вашей модели есть много лишнего, начиная от запутанной логики изменений-вставки (одно чудо в [5] чего стоит), заканчивая тем, что модель у Вас не содержит коллекций, что требует дополнительной логики в составлении пакетов изменений, например при удалении студента надо удалить все записи кросс-таблицы со ссылками на него. В Вашем случае надо "ручками" писать запрос на удаление сначала из кросс, затем из таблицы студентов.
Ну и откуда кафедры и т.д.?
Это онлайн курсы, я об этом указал
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Представьте вот такую ситуацию.
Есть некий сервис онлайн курсов "Online".
К нему есть ограниченный доступ по API (по 1 разу в 60 минут к Student и Course).
Tеоретически я хочу получать актуальную информацию с сервиса и добавлять её в свою БД, чтобы все желающие могли использовать эту информацию без ограничений.
Есть поток курса допустим 1234 "География" 23, Через месяц будет новый курс по Географии, но его id будет другим, и участвавать будут другие люди, а может некоторые будут проходить повтороно, но id у курса будет другим.

Цитата Сообщение от MsGuns Посмотреть сообщение
Когда все заполнено (точнее, отмечены отсутствующие) жмется кнопка "Сохранить"
При чём здесь грид, и кнопки сохранить? Этого всего не было в условии.


Цитата Сообщение от MsGuns Посмотреть сообщение
Что же касается "посещений" - тут явно не хватает еще 2-х таблиц:
Откуда у меня эти данные? Я же не админ этого сервиса.
Вы вообще читали условия?
0
1497 / 1238 / 245
Регистрация: 04.04.2011
Сообщений: 4,363
19.02.2021, 15:33
BeginnerCoderCS, Не в коня корм оказался. Позвольте откланяться
0
785 / 616 / 273
Регистрация: 04.08.2015
Сообщений: 1,713
19.02.2021, 16:01
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Это количество часов уже по факту пройденных заданий. Эти данные есть только на сервере.
Поскольку ваше задание не типично, то вам стоит показать все возможные данные, которые вы получаете с того сайта. И уже тогда можно будет говорить о структуре БД. Скорее всего, вы получаете не содержимое каждой конкретной таблицы на сайте, а итоговую выборку.
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
19.02.2021, 17:30  [ТС]
Цитата Сообщение от MsGuns Посмотреть сообщение
BeginnerCoderCS, Не в коня корм оказался. Позвольте откланяться
Ну надеюсь без обид

Цитата Сообщение от Igr_ok Посмотреть сообщение
Поскольку ваше задание не типично, то вам стоит показать все возможные данные, которые вы получаете с того сайта. И уже тогда можно будет говорить о структуре БД.
Я все условия в первом посте привёл с кодом и описанием.
А вообще я интересовался не структурой БД, а "оптимальной" работой с FOREIGN KEY.

На текущий момент меня устраивает вот этот ответ.
Цитата Сообщение от Cupko Посмотреть сообщение
ORM-ки то может и могут, и запрос можно постараться написать, чисто теоретически, с каким-нибудь MERGE и промежуточными таблицами. Но более оптимально сделать не выйдет. Да и на стороне СУБД это всё равно будут разные команды/инструкции.
Нет ничего криминального даже отправить 100 EXISITS в базу данных, если у вас не высоконагруженное приложение с проблемами в производительности. Иногда простой понятный код намного важнее неоптимальных запросов к БД.
Так что спасибо всем участникам обсуждения.
Если кому есть что добавить, то тема открыта 24/7
0
785 / 616 / 273
Регистрация: 04.08.2015
Сообщений: 1,713
19.02.2021, 17:48
BeginnerCoderCS, перечитал еще раз ваш первый пост...ну что сказать, ответ на него был во 2-м посте)
https://www.cyberforum.ru/ado-... st15268876
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
20.02.2021, 20:38  [ТС]
Цитата Сообщение от Igr_ok Посмотреть сообщение
перечитал еще раз ваш первый пост

Лучше позже чем никогда )))
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
06.03.2021, 00:50  [ТС]
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
C#
1
2
3
4
public Course[] GetCourses()
{
    return db.Course.ToArray();
}
Не стал создавать новую тему.
Скажите пожалуйста, в таком коде (Repostirory) нужно ли запрос обернуть в try catch?

C#
1
2
3
4
5
6
7
8
9
10
11
public Course[] GetCourses()
{
    try
    {
        return db.Course.ToArray();
    }
    catch (Exception ex)
    {
        // лог ошибки
    }
}
0
Эксперт .NET
 Аватар для Usaga
14368 / 9469 / 1360
Регистрация: 21.01.2016
Сообщений: 35,733
06.03.2021, 07:55
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Скажите пожалуйста, в таком коде (Repostirory) нужно ли запрос обернуть в try catch?
Тут действует общее правило обработки исключений: если вы знаете как обработать исключение в этом месте, то оборачивайте. Если не знаете, то не оборачивайте.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
06.03.2021, 07:55

The INSERT statement conflicted with the FOREIGN KEY
В чем собственно у меня ошибка?(На 1 скрине) п. 4.11 Правил форума

Конфликт инструкции INSERT с ограничением Foreign Key
Здравствуйте! В БД есть таблица, в которой содержатся внешние ключи с разрешенным значением NULL. Для добавления данных используется...

Конфликт инструкции INSERT с ограничением FOREIGN KEY
Здравствуйте! Есть две таблицы которые связаны ключом, при создании строки с с этим ключом SQL жалуется: Сообщение 547, уровень 16,...

Конфликт инструкции INSERT с ограничением FOREIGN KEY
вот код using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; ...

Конфликт инструкции INSERT с ограничением FOREIGN KEY
Добрый день. Помогите, пожалуйста, решить проблему. Есть БД, 2 таблицы: public class Client { public string Name {...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
28
Ответ Создать тему
Новые блоги и статьи
Установка нескольких штампов электронной подписи в строго определенных местах файла docx
ВладимирСамохин 19.07.2026
(В!) Работа с Электронной подписью - это неотъемлемая часть современного документооборота. Но что делать, если нужно поставить несколько штампов электронной подписи в строго определенных местах. . .
сукцессия 35. Научная статья о проделанной работе
anaschu 19.07.2026
Написал в формате латекс и пдф
Вангую, что это не пройдёт модерацию, и на неделе я запущу свой сервер.
Hrethgir 19.07.2026
Эта публикация сейчас в песочнице и ждёт приглашения. https:/ / habr. com/ ru/ sandbox/ 295048/ начало и оглавление - Как «пернатого» заставить осваивать новые горизонты опыта через масштабирование. . .
сукцессия 33. открытые вопросы от клауде
anaschu 19.07.2026
"Что накопилось за эту часть А — тринадцать правок, из которых шесть пришли из ваших вопросов и каждая оказалась реальной ошибкой, а не калибровкой: односторонний симбиоз, отсутствующий листопад,. . .
32 сукцессия
anaschu 19.07.2026
сукцессия 28‑мерное ядро стабилизировано Коллеги, фиксирую разбор инженерных правок и их изоморфную проекцию на экономику, меметику и половой отбор. Модель теперь не «подкручивает» сходимость —. . .
сукцессия 31: модель микоризы - это модель ещё нескольких явлений, социальных и экономических
anaschu 18.07.2026
Теория «Всего»: апдейт v1. 1. 2 — 28‑мерное ядро стабилизировано Коллеги, фиксирую разбор инженерных правок и их изоморфную проекцию на экономику, меметику и половой отбор. Модель теперь не. . .
сукцессия 30. Массив проверяющих друг друга моделей
anaschu 18.07.2026
Архитектура сети взаимопроверяющих моделей микоризной сукцессии (v2. 0) Развитие тензорного ОДУ-ядра и создание кросс-платформенного калибровочного полигона Уважаемые коллеги! В продолжение. . .
Грибы - это женщины, деревья - это мужчины. Анти инь янь для союза мужчины и женщины.
anaschu 18.07.2026
ГЛАВНЫЙ НАУЧНО-ФИЛОСОФСКИЙ ВЫВОД: Сексуально-Репродуктивный Капитализм против Государства Моногамии Коллеги, мы вышли на финишную прямую 20-мерного ОДУ-моделирования вековой сукцессии (ветка. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru