Форум программистов, компьютерный форум, киберфорум
C#: Базы данных
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.67/9: Рейтинг темы: голосов - 9, средняя оценка - 4.67
0 / 0 / 0
Регистрация: 20.09.2010
Сообщений: 23

Привязка сущности к контексту выбивает исключение

30.11.2019, 12:28. Показов 2113. Ответов 5

Студворк — интернет-сервис помощи студентам
Всем привет.
В своем проекте использую EntityFramework и столкнулся с необъяснимым мне поведением системы. Чтобы максимально упростить и не отвести от сути, попытаюсь смоделировать на простом классическом примере с использованием сущности User и Department с отношением один-ко-многим, т.е. один департамент и много пользователей. Ключевым моментом здесь является то, что ссылки на экземпляры некоторых сущностей у меня сохраняются в процессе работы, а какие-то я определяю из БД. При попытке привязать ("Attach") существующие ссылки в новом контексте у меня возникает исключение, причем такое случается только если навигационные свойства этих ссылок ссылаются на один и тот же объект (точнее если ссылаются на разные ссылки с одним и тем же id, как мы увидим по примеру далее)
Код:
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
        static void Main(string[] args)
        {
            User user1, user2;
            Department dep;
 
            using (var context = new ModelContextContainer())
            {
                dep = new Department(){Name = "Main department"};
                context.DepartmentSet.Add(dep);
 
                user1 = new User() {Name = "Director", Department = dep};
                context.UserSet.Add(user1);
 
                context.SaveChanges();
            }
 
            using (var context = new ModelContextContainer())
            {
                //1
                dep = context.DepartmentSet.FirstOrDefault(d => d.Name == "Main department");
 
                //2
                //context.DepartmentSet.Attach(dep);
 
                user2 = new User() {Name = "Accountant", Department = dep};
                context.UserSet.Add(user2);
 
                context.SaveChanges();
            }
 
            using (var context = new ModelContextContainer())
            {
                context.UserSet.Attach(user1);
                context.UserSet.Attach(user2);
 
                //Предполагается дальнейшая работа
            }
 
            Console.ReadKey();
        }
В третьем блоке использования контекста, при попытке привязать user2 выскакивает исключение:
System.InvalidOperationException: "Не удалось присоединить сущность типа "ConsoleAppEF.Department", поскольку другая сущность этого же типа уже имеет такое же значение первичного ключа.

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

Заранее скажу что здесь не используется прокси-контейнер для сущностей.
И что при поиске департамента из бд, либо "проброске" прямой ссылки, все поля абсолютно одинаковы, отличаются только разве что хэши ссылок, т.е. user1.department != user2.department.
Как же выйти из сложившейся ситуации, ведь это довольно частый случай как мне представляется, прошу дать советь.
Спасибо!
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
30.11.2019, 12:28
Ответы с готовыми решениями:

Entity Framework. Исключение при обращении к контексту сразу из двух потоков
Открыл Inner Exception: В чём суть проблемы. Есть клиент серверное приложение, использующее Entity Framework 6.0.0.0. Сервер...

Reflection выбивает исключение
Подскажите что в строке 19 и 24 сделано не так, выбивают исключения не пойму почему using System; using System.Collections.Generic; ...

Работа с рефлексией,Выбивает исключение,ругается на параметры
1) Описать класс MyClass, который будет содержать: • поля различных типов и различным уровнем доступа; • методы, с различным набором...

5
Эксперт .NET
 Аватар для Usaga
14307 / 9388 / 1355
Регистрация: 21.01.2016
Сообщений: 35,405
30.11.2019, 13:32
C#
1
2
3
4
5
6
7
8
9
10
using (var context = new ModelContextContainer())
            {
                dep = new Department(){Name = "Main department"};
                context.DepartmentSet.Add(dep);
 
                user1 = new User() {Name = "Director", Department = dep};
                context.UserSet.Add(user1);
 
                context.SaveChanges();
            }
Не надо называть коллекции DepartmentSet. Назовите Departments. Оно и так понятно, что это набор из множества разных департаментов.

Не надо отдельно добавлять департамент. Когда EF увидит, что User имеет ссылку на объект не входящий в контекст, то автоматически навести на его состояние Added и при вызове SaveChanges() автоматом добавит его в базу. Т.е. должно выглядеть так:

C#
1
2
3
4
5
6
7
8
            using (var context = new ModelContextContainer())
            {
                var dep = new Department { Name = "Main department" };
                var user = new User { Name = "Director", Department = dep };
                context.Users.Add(user);
 
                context.SaveChanges();
            }
Цитата Сообщение от ProgerLink Посмотреть сообщение
В третьем блоке использования контекста, при попытке привязать user2 выскакивает исключение:
System.InvalidOperationException: "Не удалось присоединить сущность типа "ConsoleAppEF.Department", поскольку другая сущность этого же типа уже имеет такое же значение первичного ключа.
Ну, блин, логично! Два объекта пользователей ссылаются на два идентичных объекта департамента. EF это видит и даёт вам отлуп. Вы какое-то иное поведение тут ожидали?
0
0 / 0 / 0
Регистрация: 20.09.2010
Сообщений: 23
30.11.2019, 15:36  [ТС]
Usaga, привет. Спасибо за отклик.
Данный пример смоделирован для отладки возникшей ошибки и определения ее природы, как система предложила обозвать "набор", так и оставил, да и сути вопроса это не решает.
У меня на деле первый и второй блоки контекста объеденены по принципу, если существует в бд запись, верну ее, если нет то создам, добавлю в бд и верну ее.

Природа возникновения исключения ясна, разные экземпляры сущностей для фреймворка принципиальны, но как теперь выйти то из ситуации ???
Держать в памяти кэш всех департаментов и первостепенно проверять в нем ? Но это не комильфо, в моем реальном случае у каждого юзера будет свой департамент (в боевом проекте это совсем другое).
Я к тому что может есть возможность повлиять как то на EF или его механизмы?

Стейтами (свойство "State" у "Entry") пробовал по разному, не помогло.
0
Эксперт .NET
 Аватар для Usaga
14307 / 9388 / 1355
Регистрация: 21.01.2016
Сообщений: 35,405
30.11.2019, 16:17
ProgerLink, повлиять на EF очень просто. Достаточно его не обманывать. Если вы знает ID департамента, то именно ID и выставляйте.
0
0 / 0 / 0
Регистрация: 20.09.2010
Сообщений: 23
30.11.2019, 22:23  [ТС]
Usaga, попробовал я и такой способ, добавил помимо навигационного свойства и явный внешний ключ в сущности User на сущность Department, а при создании "пользователей" указываю не ссылки на департаменты, а их Ид:
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
        static void Main(string[] args)
        {
            User user1, user2;
            Department dep;
 
            using (var context = new ContextDB())
            {
                dep = new Department() { Name = "Main department" };
                context.DepartmentSet.Add(dep);
 
                user1 = new User() { Name = "Director", DepartmentId = dep.Id };
                context.UserSet.Add(user1);
 
                context.SaveChanges();
            }
 
            using (var context = new ContextDB())
            {
                dep = context.DepartmentSet.FirstOrDefault(d => d.Name == "Main department");
 
                user2 = new User() { Name = "Accountant", DepartmentId = dep.Id };
                context.UserSet.Add(user2);
 
                context.SaveChanges();
            }
 
            using (var context = new ContextDB())
            {
                context.UserSet.Attach(user1);
                context.UserSet.Attach(user2);
            }
 
            Console.ReadKey();
        }
Но это нисколько не решило проблему. Приведу на всякий случай описание классов сущностей:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
 
        public int DepartmentId { get; set; }
        public Department Department { get; set; }
    }
 
    public class Department
    {
        public Department()
        {
            this.Users = new List<User>();
        }
 
        public int Id { get; set; }
        public string Name { get; set; }
 
        public ICollection<User> Users { get; set; }
    }
Добавлено через 3 часа 0 минут
Найдено решение, во втором блоке работы с контекстом при поиске департамента в данном примере по имени, необходим вызов "AsNoTracking()" :
C#
1
                dep = context.DepartmentSet.AsNoTracking().FirstOrDefault(d => d.Name == "Main department");
Хотя не до конца понимаю происходящего, ну не включили мы в текущий контекст трэкинг изменений результат поиска, но ведь в третьем блоке контекста, старый контекст уже не существует, а сама сущность не содержит никакого признака этой операции. Хранит где-то в статике ? Но я считал что все действия происходят только в рамках текущего контекста.
Если кто понимает происходящее, прошу разжевать, буду очень признателен.

Тем не менее, после этого можно приатачить сущности, менять их поля, в том числе поля самого департамента и при SaveChanges() EF запишет все изменения.
0
Эксперт .NET
 Аватар для Usaga
14307 / 9388 / 1355
Регистрация: 21.01.2016
Сообщений: 35,405
01.12.2019, 08:04
Цитата Сообщение от ProgerLink Посмотреть сообщение
Но это нисколько не решило проблему.
Конечно не решило. EF реализует паттерн Identity Map. Т.е. отслеживает все сущности и их ID. Когда вы делаете такое:

C#
1
2
3
4
5
6
7
8
9
            using (var context = new ContextDB())
            {
                dep = context.DepartmentSet.FirstOrDefault(d => d.Name == "Main department");
 
                user2 = new User() { Name = "Accountant", DepartmentId = dep.Id };
                context.UserSet.Add(user2);
 
                context.SaveChanges();
            }
EF ищет в своём кеше сущность Department с Id, который вы казали в User и втыкает ссылку на него в сущность User. Вы это может проверить после вызова SaveChanges. Поэтому вы возвращаетесь к изначальной проблеме ровно в том виде, в каком она и была.

Во втором запросе вам не нужна вся сущность Department целиком. Запросите просто её Id и всё. Это и правильнее с точки зрения EF'а и производительнее.

Добавлено через 1 минуту
Ну и основное правильно работы с EF: сущности не должны кочевать между контекстами.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
01.12.2019, 08:04
Помогаю со студенческими работами здесь

Directory.Move() выбивает исключение, подскажите что не так
Функция по перемещению директории выбивает ошибку, подскажите- что не так? static void DirectMove() { string...

Как правильно отобразить на экране сущности и добавление полей к сущности
Есть примеры привязки бд с не тепезированным набором данных Как правильно отобразить на экране сущности и добавление полей к сущности

СМА Indesit WITL106(EU)/Y S/N 091342680075, не блокирует УБЛ. ошибку не выбивает.Ошибку не выбивает
Проблема изначальная сма не подавала признаков жизни, произвел замену процессора с прошивкой под данную сма. Нажимаю теперь кнопку вкл...

реклама не по контексту а по по последним поискам...
Вопрос таков: я часто вижу на своем сайте не релевантные рекламные сообщения которые и близко не стояли по контексту и я вношу их чуть ли...

Binding в DataGridComboBoxColumn к корневому контексту
Всем здраствуйте, разбираюсь с WPF делаю самообучающий проект, возникла проблема с привязкой. Помогите кто разбирается. Есть окно...


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

Или воспользуйтесь поиском по форуму:
6
Ответ Создать тему
Новые блоги и статьи
Оттенки серого
Argus19 18.03.2026
Оттенки серого Нашёл в интернете 3 прекрасных модуля: Модуль класса открытия диалога открытия/ сохранения файла на Win32 API; Модуль класса быстрого перекодирования цветного изображения в оттенки. . .
SDL3 для Desktop (MinGW): Рисуем цветные прямоугольники с помощью рисовальщика SDL3 на Си и C++
8Observer8 17.03.2026
Содержание блога Финальные проекты на Си и на C++: finish-rectangles-sdl3-c. zip finish-rectangles-sdl3-cpp. zip
Символические и жёсткие ссылки в Linux.
algri14 15.03.2026
Существует два типа ссылок — символические и жёсткие. Ссылка в Linux — это запись в каталоге, которая может указывать либо на inode «файла-ИСТОЧНИКА», тогда это будет «жёсткая ссылка» (hard link),. . .
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора ВВЕДЕНИЕ Выполняя задание на управление насосной группой заполнения резервуара,. . .
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога Финальные проекты на Си и на C++: hello-sdl3-c. zip hello-sdl3-cpp. zip Результат:
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru