Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.95/64: Рейтинг темы: голосов - 64, средняя оценка - 4.95
4 / 5 / 1
Регистрация: 25.05.2012
Сообщений: 262
Записей в блоге: 1

Entity Core: Перед записью проверить данные на уникальность

31.10.2018, 13:21. Показов 13695. Ответов 29

Студворк — интернет-сервис помощи студентам
Добрый день.
Столкнулся с проблемой - хочу чтобы в таблице были только уникальные значения. Решил проверять нет ли уже такой записи в таблице. Для проверки написал расширения для класса Db. В моей БД 4 таблицы - в две из них пишу всё подряд, а в две другие - только то, чего там еще нет.
(Все четыре модели у меня реализуют пустой интерфейс IDbData. Поэтому приходится в условном операторе определять тип переданного объекта.)

Вот код:
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
        static public void AddUniqueOnly(this Db db, IDbData item)
        {
                if (item.GetType() == typeof(RootCertInfo))
                {
                    var equalRecords = db.RootCertInfos.Where(x => x.Equals((RootCertInfo)item));
                    if (equalRecords.Count()!=0) // System.NullReferenceException: "Ссылка на объект не указывает на экземпляр объекта."
                    {
                        db.Add(item);
                        return;
                    }
 
                }
                if (item.GetType() == typeof(CrlUrl))
                {
                    var equalRecords = db.CrlUrls.Where(x => x.Equals((CrlUrl)item));
                    if (equalRecords.Count()!=0)
                    {
                        db.Add(item);
                        return;
                    }
                }
 
            db.Add(item);
 
        }
Код выдает ошибку: System.NullReferenceException: "Ссылка на объект не указывает на экземпляр объекта."

Пошаговый дебаг не прояснил ситуацию. Возможно я не верно понимаю принцип работы EntityFramework?
Я решил проверять объекты на равенство друг другу:

C#
1
db.RootCertInfos.Where(x => x.Equals((RootCertInfo)item))
Я думал, что при запросе к таблице БД я получу список объектов (строк), которые равны - все поля одинаковые - с переданным объектом. Но так ли это работает?

И вообще насколько это корректно? Может быть у Entity есть какой-то свой способ?

Заранее спасибо.
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
31.10.2018, 13:21
Ответы с готовыми решениями:

Как проверить ID записи на уникальность перед занесением в базу данных?
ИСПОЛЬЗУЕТСЯ: С#, Access ИМЕЕТСЯ: В Access по средствам кода добавляются записи в БД с определённым ID. Как проверить на...

проверить pipe перед записью
как перед записью проверить, что пайп пустой? Мне необходимо передать потомку три строки разной длины. Так вот, родитель пихает в пайп...

Перед записью проверить существование или пустоту файла
Делаю так char * fileName = "out.txt"; // Путь к файлу FILE * file = fopen(fileName, "a"); char sentence; if (fgets(sentence,...

29
4 / 5 / 1
Регистрация: 25.05.2012
Сообщений: 262
Записей в блоге: 1
06.11.2018, 16:39  [ТС]
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от Usaga Посмотреть сообщение
Я вам рекомендую делать это самостоятельно и явно. Открыть транзакцию, проверить существование записи с определённым содержимым поля, добавить запись в контекст. После того, как наберётся N записей, "сбросить" их в базу через SaveChanges(). Будет N ходок в базу, для проверки каждой записи, но зато по фен-шую.
Вы предлагаете мне тоже самое, что я описал в первом посте этой темы.

Вы мне тогда ответили:
Цитата Сообщение от Usaga Посмотреть сообщение
skitale, проверять на уникальность умеют все СУБД, даже всякие SQLite и Firebird. Причём уже триста лет подряд. Это не вопрос EF'а. Навешивайте на уникальные поля индекс уникальности (это в базе делается).
Установка индекса уникальности на конкретном поле таблицы не спасает - БД возвращает ошибку, а EF в ответ на нее перестает писать в базу вообще что-либо.
Поправьте меня, если я не прав, но кажется мы ходим по кругу.
0
Эксперт .NET
 Аватар для Usaga
14368 / 9469 / 1360
Регистрация: 21.01.2016
Сообщений: 35,733
06.11.2018, 16:43
skitale, нет. Я вам предлагаю не просто самому в коде проверку делать, но делать это внутри транзакции, а на полях требующих уникальности должны висеть индексы уникальности. Это сильно не тоже самое.
0
4 / 5 / 1
Регистрация: 25.05.2012
Сообщений: 262
Записей в блоге: 1
06.11.2018, 16:59  [ТС]
Цитата Сообщение от Usaga Посмотреть сообщение
Я вам предлагаю не просто самому в коде проверку делать, но делать это внутри транзакции
Поясните в чем разница, пожалуйста.
0
Эксперт .NET
 Аватар для Usaga
14368 / 9469 / 1360
Регистрация: 21.01.2016
Сообщений: 35,733
06.11.2018, 17:03
skitale, разница в словах выделенных жирным текстом. Индекс уникальности ускорит (в десятки раз) поиск по полям с ним, а уникальность защитит поле от неуникального значения на уровне СУБД (если в коде ошибётесь). Транзакция заблокирует доступ к индексу параллельным транзакциям, что защитит данные на время выполнения текущей транзакции (параллельно никто не сможет запихнуть данные, чью уникальность вы только что проверили, но запись в базу ещё не совершили).

Ну и если такой подход покажет плохую производительность (и только в этом случае), то можно прибегнуть к чистому SQL.
0
4 / 5 / 1
Регистрация: 25.05.2012
Сообщений: 262
Записей в блоге: 1
06.11.2018, 17:41  [ТС]
Код ниже выдает ту же ошибку:

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
public void SendToDb(List<IDbData> dataObject)
        {
            Db db = new Db();
            foreach (IDbData item in dataObject)
            {
 
                Write($"Пишу данные в таблицу {item.GetType().ToString()}");
 
                using (var transaction = db.Database.BeginTransaction())
                {
 
                    if (item.GetType() == typeof(CrlInfo))
                    {
                        var equalRecords = db.CrlInfos.Where(x => x.UcName == ((CrlInfo)item).UcName);
                        if (equalRecords != null && equalRecords.Count() != 0)
                        {
                            db.Add(item);
                            db.SaveChanges();
                            transaction.Commit();
                        }
                        else
                        {
                            Console.WriteLine($"Запись с UCName = {((CrlInfo)item).UcName} не уникальна");
                        }
                    }
                }
            }
 
            Console.WriteLine($"Запись в базу произведена");
        }
Добавлено через 10 минут
Usaga, хоть убейте я не могу понять как предложенный вами вариант решает заявленную в теме проблему.

Индекс ускоряет поиск - прекрасно, но у меня не было проблем со скоростью поиска.
Использование транзакции предотвращает параллельную запись в базу - очень хорошо, но я пишу в базу синхронно в один поток. От кого блокировать базу?

Прежде вы говорили, что СУБД сама проверит данные на уникальность с помощью индекса - да, она проверяет, но EF не знает что с этим делать дальше и из 1500 записей записывает 12, натыкается на ошибку СУБД и дальше не пишет вообще ничего. По-моему в этом проблема, а не в скорости работы или взаимной блокировке.

Вы уже предлагали делать "неправославно":
Цитата Сообщение от Usaga Посмотреть сообщение
Имея такой индекс на поле таблицы (можно на разных полях) вы можете: .... Или можете на пофиг пытаться вставить запись без всяких проверок. СУБД сама выполнит проверку уникальности и если что, то выдаст вам ошибку. Второе проще, но так делать не принято)
Так вот этот вариант у меня почему-то не работает. Скорее всего дело в моих кривых руках, а как сделать правильно?
Пусть долго и неэффективно, но чтобы оно заработало в конце-концов.
0
Эксперт .NET
 Аватар для Usaga
14368 / 9469 / 1360
Регистрация: 21.01.2016
Сообщений: 35,733
06.11.2018, 17:56
skitale, код отвратителен чуть более, чем полностью. Ещё и работает он задомнаперёд: он только дубликаты пытается записать.

Если сохранить авторский стиль, то так должно быть чуть лучше:

Кликните здесь для просмотра всего текста

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
public void SendToDb(IEnumerable<IDbData> dataObjects)
{
    var db = new Db();
    
    using (var transaction = new TransactionScope())
    {
        foreach (var dataObject in dataObjects)
        {
            if (dataObject is CrlInfo item)
            {
                var name = item.UcName;
 
                var hasWithSameName = db.CrlInfos.Any(x => x.UcName == name);
                if (!hasWithSameName)
                {
                    db.Add(item);
                }
            }
        }
 
        db.SaveChanges();
        transaction.Complete();
    }
}


Цитата Сообщение от skitale Посмотреть сообщение
Индекс ускоряет поиск - прекрасно, но у меня не было проблем со скоростью поиска.
Использование транзакции предотвращает параллельную запись в базу - очень хорошо, но я пишу в базу синхронно в один поток. От кого блокировать базу?
Сделать нормально можно сразу, а не дожидаться, когда всё начнут тормозить и данные теряться. Благо, что я не предлагаю вторую "Войну и Мир" написать, а навесить индекс на поле в базе (одна строка к скрипту создания базы) и защититься от проблем конкурентного доступа (ещё пара строк в код). Это такой труд непосильный?

Цитата Сообщение от skitale Посмотреть сообщение
Так вот этот вариант у меня почему-то не работает.
"Почему-то" мне ничего не говорит о том, что у вас там пошло не так.
0
4 / 5 / 1
Регистрация: 25.05.2012
Сообщений: 262
Записей в блоге: 1
06.11.2018, 18:16  [ТС]
Цитата Сообщение от Usaga Посмотреть сообщение
Сделать нормально можно сразу
Полностью согласен. Но у меня не получилось.

Цитата Сообщение от Usaga Посмотреть сообщение
"Почему-то" мне ничего не говорит о том, что у вас там пошло не так
Понимаю вас, но я потому и написал, что не мог найти что у меня пошло не так.

Цитата Сообщение от Usaga Посмотреть сообщение
код отвратителен чуть более, чем полностью. Еще и работает он задомнаперёд: он только дубликаты пытается записать.
Да, действительно - спасибо, что заметили. Видимо именно в этом и была изначально моя ошибка. Ваш код работает прекрасно - и с индексом и без. Спасибо вам за терпение. А как сделать его лучше и чем именно он отвратителен, на ваш взгляд?
0
Эксперт .NET
 Аватар для Usaga
14368 / 9469 / 1360
Регистрация: 21.01.2016
Сообщений: 35,733
07.11.2018, 06:16
Цитата Сообщение от skitale Посмотреть сообщение
Понимаю вас, но я потому и написал, что не мог найти что у меня пошло не так.
Ну вы же как-то поняли, что код некорректный?

Ошибка на этапе компиляции? Если да, то каков её текст?

Исключение во время работы? Если да, то на какой строке, каково название исключения и текст из его свойства Message?

Код молча отрабатывает, но делает не то, что нужно? Пройдите по коду под отладчиком (debugger) шаг за шагом и посмотрите, что на самом деле делает ваш код.

Цитата Сообщение от skitale Посмотреть сообщение
А как сделать его лучше и чем именно он отвратителен, на ваш взгляд?
Конкретно ваш фрагмент кода:
* метод в параметрах принимает конкретный тип List<T>, хотя использует только возможности реализуемого им интерфейса IEnumerable<T>. Принимайте в параметрах настолько общие интерфейсы\классы, насколько это возможно.
* принимаемая в параметрах коллекция содержит объекты какого-то сильно общего интерфейса IDbData, реальный тип которого вы довольно кривым образом проверяете, а потом кастуете к нужному типу. В 99.9% случаев можно было сделать нормально и передавать коллекцию нужных объектов, без проверок и кастования.
* класс контекста (Db) назван безобразно. Давайте классам говорящие имена, отражающие их суть и назначение.
* Db создаётся прямо в методе. Такое допустимо только в совсем крохотных приложениях. В остальных случаях Db желательно завернуть в репозиторий (паттерн такой) и принимать в конструкторе класса, в методе которого это дело и используется. Иначе формируется очень жёсткая связь между классами.
* метод SendToDb пишет прямо в консоль. Это фу ещё хуже, чем в пункте выше. Заведите интерфейс ILogger (название на усмотрение), реализация которого и будет писать куда вам надо. Внедряйте Logger или через IoC (желательно) или через синглтон (сильно проще, чем IoC, приемлемо в небольших проектах).
* кодом вида

C#
1
2
var equalRecords = db.CrlInfos.Where(x => x.UcName == ((CrlInfo)item).UcName);
if (equalRecords != null && equalRecords.Count() != 0)
вы заставляли СУБД перебирать ВСЕ ДО ЕДИНОЙ записи в таблице и проверять их имена. Да и проверка на null тут бесполезна.
* вы на каждой итерации цикла создаёте новую транзакцию и совершаете запись в базу. Если записей не миллионы, то сильно быстрее было бы (в плане скорости работы с базой) завести одну транзакцию на весь цикл и по окончанию цикла, сохранить изменения (разом) и завершить транзакцию.
1
4 / 5 / 1
Регистрация: 25.05.2012
Сообщений: 262
Записей в блоге: 1
07.11.2018, 13:38  [ТС]
Usaga, огромное спасибо за развернутый ответ! Многое не понял, так что ушел читать матчасть

Добавлено через 5 минут
Цитата Сообщение от Usaga Посмотреть сообщение
принимаемая в параметрах коллекция содержит объекты какого-то сильно общего интерфейса IDbData, реальный тип которого вы довольно кривым образом проверяете, а потом кастуете к нужному типу. В 99.9% случаев можно было сделать нормально и передавать коллекцию нужных объектов, без проверок и кастования.
Да, согласен, что это всё как-то криво, но я решил, что это неплохое решение для того чтобы метод принимал объект любого из четырех типов модели. А как можно было сделать иначе?
0
Эксперт .NET
 Аватар для Usaga
14368 / 9469 / 1360
Регистрация: 21.01.2016
Сообщений: 35,733
07.11.2018, 14:17
Цитата Сообщение от skitale Посмотреть сообщение
А как можно было сделать иначе?
Обобщённый репозиторий.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
07.11.2018, 14:17

Проверить данные перед обновлением
Добрый вечер. Ситуация такая: есть detailsView, при обновлении два параметра берёт из установленных в нём полей и два - из контролов. ...

Проверить данные перед отправкой ajax запроса
Добрый день! Подскажите пожалуйста, на теге &lt;a/&gt;, на класс delete_media висит обработчик, который собирает data параметры и отправляет ajax...


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

Или воспользуйтесь поиском по форуму:
30
Ответ Создать тему
Новые блоги и статьи
Установка нескольких штампов электронной подписи в строго определенных местах файла 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