Форум программистов, компьютерный форум, киберфорум
Наши страницы
C#: ASP.NET MVC
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.50/4: Рейтинг темы: голосов - 4, средняя оценка - 4.50
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
1

Дублирование в другую таблицу при добавлении объекта

26.02.2018, 16:53. Просмотров 649. Ответов 19

Пишу маленький проект с многоуровневой архитектурой. Использую EF, automapper, для маппинга дто моделей. Возникла проблема при добавлении объекта со связью many-to-many.
Есть две сущности Album и Genre:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Album
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<genre> Genres { get; set; }
 
    public Album()
    {
        Genres = new List<genre>();
    }
}
 
public class Genre
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<album> Albums { get; set; }
    public Genre()
    {
        Albums = new List<album>();
    }
}
А так же DTO сущности:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AlbumDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<genre> Genres { get; set; }
    public AlbumDTO()
    {
        Genres = new List<genre>();
    }
}
 
public class GenreDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<album> Albums { get; set; }
 
    public GenreDTO()
    {
        Albums = new List<album>();
    }
}
В контроллере делаю добавление объекта AlbumDTO, маплю, для приведения типов.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public ActionResult Create(AlbumDTO albumDTO, int[] selectedGenres)
{
    if(selectedGenres != null)
    {
        var config = new MapperConfiguration(cfg => cfg.CreateMap<genredto, genre="">());
        IMapper mapper = config.CreateMapper();
        List<genre> genres = mapper.Map<ienumerable<genredto>, List<genre>>(genreService.GetAll());
 
    foreach(Genre g in genres.Where(gen => selectedGenres.Contains(gen.Id)))
    {
        albumDTO.Genres.Add(g);
    }
    }
albumService.Add(albumDTO);
return RedirectToAction("Index");
}
Метод добавления объекта AlbumDTO, мапим к типу Album и передаем на уровень DAL:
C#
1
2
3
4
5
6
7
8
public void Add(AlbumDTO albumDTO)
{
    Mapper.Initialize(cfg => cfg.CreateMap<albumdto, album="">());
    var album = Mapper.Map<albumdto, album="">(albumDTO);
 
    Database.Albums.Add(album);
    Database.Save();
}
Далее в репозитории просто сохраняем объект в БД. Album в бд записывается без проблем, а так же id-шки в автоматом созданную таблицу AlbumGenres (code first). Но в таблицу Genres записываются жанры, которые мы получили из представления (выбрали для добавления) и которые добавляем в albumDTO.Genres.Add(g) в контроллере. В чем может быть проблема? Грешу на модель DTO и на мапинг... Но не знаю точно в чем дело. Может поможет кто
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
26.02.2018, 16:53
Ответы с готовыми решениями:

При добавлении объекта в бд у поля ID слетает свойство identity
В модели имеем id: public Int64 Id { get; set; } В бд изначально создал табличку, с полем...

Ошибка при добавлении строки в таблицу
Привет! Разместила свой сайт на сервере Следующая инструкция стала вызывать ошибку (хотя при...

Ошибка при добавлении данных в таблицу БД
Здравствуйте! У меня имеется страничка ASP, которая работает с БД через модель ADO.NET. На...

При добавлении записи в таблицу (Access) дублирует результат. Кидаю пример кода...
Помогите плз. При добавлении записи в таблицу (Access) дублирует результат. Кидаю пример кода.....

Дублирование праймари при добавлении в таблицу
у меня 3 таблицы, в каждую из них я обращаюсь по очереди. Сначала добавляю пользователя в klients ...

19
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
26.02.2018, 17:14 2
leikin28, вам нужно приаттачить сущности к контексту, чтобы EF воспринимал их как существующие. Что-то типа:
C#
1
2
3
4
foreach (var genre in album.Genres)
{
     Database.Genres.Attach(genre);
}
0
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
27.02.2018, 11:33  [ТС] 3
Cupko, добавил Attach, в метод создания таким образом
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
        public void Add(AlbumDTO albumDTO)
        {
            var conf = new MapperConfiguration(cfg => cfg.CreateMap<AlbumDTO, Album>());
            IMapper mapper = conf.CreateMapper();
            var album = mapper.Map<AlbumDTO, Album>(albumDTO);
 
            foreach (var genre in album.Genres)
            {
                Database.Genres.Attach(genre);
            }
 
            Database.Albums.Add(album);
            Database.Save();
        }
А в DAL в классе репозитория создал метод Attach
C#
1
2
3
4
        public void Attach(Genre genre)
        {
            db.Genres.Attach(genre);
        }
Вроде первый раз добавляет, но потом на строке db.Genres.Attach(genre); выдает исключение "An entity object cannot be referenced by multiple instances of IEntityChangeTracker".
Я так понимаю, что ошибка в контексте данных...
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
27.02.2018, 11:47 4
leikin28, видимо, уже зааттачена.
А так?
C#
1
2
3
4
5
var entityEntry = db.Entry(genre);
if (entityEntry == null)
{
     db.Genres.Attach(genre);
}
leikin28, а вообще, походу сущность зааттачена к другому контексту. У вас как контекст инициализируется? DI-фреймворком?
0
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
27.02.2018, 14:11  [ТС] 5
Cupko, не, тоже выбивает исключение.
Да, использую ninject.
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
27.02.2018, 15:52 6
leikin28, я так полагаю, вам нужно инжектить один экземпляр контекста на каждый веб-запрос. Это должно помочь.
0
Usaga
Эксперт .NET
5311 / 3618 / 633
Регистрация: 21.01.2016
Сообщений: 14,382
Завершенные тесты: 2
28.02.2018, 08:10 7
Цитата Сообщение от Cupko Посмотреть сообщение
я так полагаю, вам нужно инжектить один экземпляр контекста на каждый веб-запрос
А вот это уже вредный совет. Контекст должен жить как можно меньше. Создали, поработали, задиспозили.
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
28.02.2018, 09:15 8
Usaga, не могу согласиться. Это ж вам не постоянно открытое соединение.

Тут вам и трекер изменений, и одно соединение с одним запросом в транзакции на SaveChanges - прям UoW из коробки.

Может и можно задизайнить так, чтоб нормально сущности аттачились к разным контекстам, но что-то такое не припомню/не встречал.
0
Usaga
Эксперт .NET
5311 / 3618 / 633
Регистрация: 21.01.2016
Сообщений: 14,382
Завершенные тесты: 2
28.02.2018, 09:19 9
Cupko, состояние и так не будет разрываться (пул подключений). А вот то, что кеш трекера будет со временем раздуваться - из рук вон плохо, ибо прямо ударит по производительности, ведь при сохранении контексту придётся ВЕСЬ кеш перебирать в поисках изменений. Оно того тупо не стоит.

В чём проблема подключить к новому контексту сущность пришедшую с клиента?
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
28.02.2018, 09:41 10
Usaga, я ж вам не говорю его Синглтоном делать, да и мы в ASP.NET ветке сидим. Один веб запрос - одна бизнес транзакция.

Цитата Сообщение от Usaga Посмотреть сообщение
В чём проблема подключить к новому контексту сущность пришедшую с клиента?
Не хотят у автора намапленные одинаковые child-сущности аттачиться в разные контексты.
0
Usaga
Эксперт .NET
5311 / 3618 / 633
Регистрация: 21.01.2016
Сообщений: 14,382
Завершенные тесты: 2
28.02.2018, 09:43 11
Цитата Сообщение от Cupko Посмотреть сообщение
я ж вам не говорю его Синглтоном делать
Я так понял, что именно это вы ТС-у и предложили. Это не так? Может я вас неверно понял?

Цитата Сообщение от Cupko Посмотреть сообщение
Не хотят у автора намапленные одинаковые child-сущности аттачиться в разные контексты.
По моему это раньше было умешленным ограничением. Сейчас этого уже нет (вроде бы). Этой проблемы бы не было, если бы контекст диспозился сразу после запроса, как я и рекомендую.
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
28.02.2018, 10:01 12
Цитата Сообщение от Usaga Посмотреть сообщение
Я так понял, что именно это вы ТС-у и предложили. Это не так? Может я вас неверно понял?
DI-фреймворки могут в каждый Webrequest инжектить один экземпляр, что, вроде, я и скинул.
0
Usaga
Эксперт .NET
5311 / 3618 / 633
Регистрация: 21.01.2016
Сообщений: 14,382
Завершенные тесты: 2
28.02.2018, 10:03 13
Cupko, ну это нормальный вариант. Один контекст на один запрос.
0
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
28.02.2018, 15:11  [ТС] 14
Cupko, я так понял, что ошибка возникает из-за того, что в данный момент используется два контекста данных? Я использую паттерн UOW, а в нем, если я не ошибаюсь, используется один контекст данных для всех репозиториев.
Тогда выходит ошибка в нем, или все же в DI?
Или я все вообще неправильно понял
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
28.02.2018, 15:29 15
Цитата Сообщение от leikin28 Посмотреть сообщение
ошибка возникает из-за того, что в данный момент используется два контекста данных
Я предполагаю что так и есть.
Цитата Сообщение от leikin28 Посмотреть сообщение
Я использую паттерн UOW, а в нем, если я не ошибаюсь, используется один контекст данных для всех репозиториев.
тут, собственно, как вы настроите DI/инициализируете контекст, так оно и будет. Что значит используется один контекст, откуда такая уверенность?
Цитата Сообщение от leikin28 Посмотреть сообщение
Или я все вообще неправильно понял
Все вроде правильно поняли
0
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
28.02.2018, 16:23  [ТС] 16
Cupko, ну по самой идее паттерна все репозитории используют один и тот же контекст данных.
Кстати, почему-то когда делаю Attach перед добавлением на чистую базу, то все проходит хорошо, потом если пробую еще раз добавлять и выбираю несколько объектов типа Genre, что выбирал до этого для добавления, то выдает исключение) Так и должно быть?

Цитата Сообщение от Cupko Посмотреть сообщение
тут, собственно, как вы настроите DI/инициализируете контекст, так оно и будет. Что значит используется один контекст, откуда такая уверенность?
Инициализация контекста происходит в классе UnitOfWork и передается в свойства
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
public class UnitOfWork : IUnitOfWork
    {
        private DataContext context;
        private AlbumRepository aRepository;
        private OrderRepository oRepository;
        private Repository<MediaType> mtRepository;
        private GenreRepository gRepository;
        private bool isDisposed = false;
 
        public UnitOfWork(string connectionString)
        {
            context = new DataContext(connectionString);
        }
        public IAlbumRepository Albums
        {
            get
            {
                if (aRepository == null)
                    aRepository = new AlbumRepository(context);
                return aRepository;
            }
        }
        public IGenreRepository Genres
        {
            get
            {
                if (gRepository == null)
                    gRepository = new GenreRepository(context);
                return gRepository;
            }
        }
}
На уровне BL в классе сервиса
C#
1
2
3
4
5
6
7
8
    public class AlbumService : IAlbumService
    {
        private IUnitOfWork Database { get; set; }
        public AlbumService(IUnitOfWork uow)
        {
            Database = uow;
        }
}
А также настраивает зависимость
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ServiceModule : NinjectModule
    {
        private string connectionString;
 
        public ServiceModule(string connection)
        {
            connectionString = connection;
        }
 
        public override void Load()
        {
            Bind<IUnitOfWork>().To<UnitOfWork>().WithConstructorArgument(connectionString);
        }
    }
И уже при работе с контроллером работаем через объект сервиса
C#
1
2
3
4
5
6
7
8
9
10
    public class AlbumController : Controller
    {
        private IAlbumService albumService;
        private IService<GenreDTO> genreService;
 
        public AlbumController(IAlbumService service, IService<GenreDTO> gService)
        {
            albumService = service;
            genreService = gService;
        }
И в описываем зависимости
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public class NinjectDependencyResolver : IDependencyResolver
    {
        private IKernel kernel;
 
        public NinjectDependencyResolver(IKernel param)
        {
            kernel = param;
            AddBindings();
        }
 
        private void AddBindings()
        {
            kernel.Bind<IOrderService>().To<OrderService>();
            kernel.Bind<IAlbumService>().To<AlbumService>();
            kernel.Bind<IService<GenreDTO>>().To<GenreService>().InRequestScope();
            kernel.Bind<IService<MediaTypeDTO>>().To<MediaTypeService>();
        }
}
Для большей понятности могу скинуть архив проекта.
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
28.02.2018, 17:12 17
leikin28, ох, как все странно у вас.
Только что заметил, что в DTO child-коллекции содержат EF объекты, а не DTO
Судя по конфигурации, у вас в каждый сервис инжектится свой UoW объект, который имеет свой контекст.
Т.е. вы достаете из GenreService список жанров привязанный к одному контексту, добавляете эти жанры в свежесмаппленный Album, и запуливаете в AlbumService с другим экземпляром контекста эти жанры. Видимо, отсюда и ошибка.

Поставьте InRequestScope() на UoW. Либо маппайте в DTO-хи child-сущности.

Цитата Сообщение от leikin28 Посмотреть сообщение
ну по самой идее паттерна все репозитории используют один и тот же контекст данных.
Все верно, но реализовать его можно по-разному

Я предполагал, что вы используете DI/IoC для управлением жизни контекста и репозиториев в том числе. Зачем вам тогда нужен DI-фреймворк, если вы ручками инициализируете контексты и репозитории?
0
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
01.03.2018, 15:39  [ТС] 18
Cupko,
Цитата Сообщение от Cupko Посмотреть сообщение
Только что заметил, что в DTO child-коллекции содержат EF объекты, а не DTO
Цитата Сообщение от Cupko Посмотреть сообщение
Либо маппайте в DTO-хи child-сущности.
А можно поподробнее? Не совсем уловил
0
Cupko
462 / 451 / 127
Регистрация: 17.07.2012
Сообщений: 1,341
Записей в блоге: 1
Завершенные тесты: 2
01.03.2018, 15:52 19
Цитата Сообщение от leikin28 Посмотреть сообщение
А можно поподробнее? Не совсем уловил
Это я сгоряча и не подумав Не поможет в этом случае. Всё равно контекст будет трэкать их, пока вы его не задиспозите. Можно еще попробовать убрать трэкинг при помощи AsNoTracking(). Но тогда Update методы в репозиториях превратятся в ад...
0
leikin28
0 / 0 / 0
Регистрация: 27.09.2017
Сообщений: 9
02.03.2018, 11:31  [ТС] 20
Cupko, короче, я не придумал ничего лучше, чем передать массив ID выбранных жанров в класс сервиса и там сделать выборку в пределах одного контекста

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void Add(AlbumDTO albumDTO, int[] selectedGenres)
        {
            var conf = new MapperConfiguration(cfg => cfg.CreateMap<AlbumDTO, Album>());
            IMapper mapper = conf.CreateMapper();
            var album = mapper.Map<AlbumDTO, Album>(albumDTO);
 
            if (selectedGenres != null)
            {
                foreach (var genre in Database.Genres.GetAll().Where(g => selectedGenres.Contains(g.Id)))
                {
                    album.Genres.Add(genre);
                }
            }      
 
            Database.Albums.Add(album);
            Database.Save();
        }
0
02.03.2018, 11:31
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
02.03.2018, 11:31

Триггер на обновление поля при добавлении записи в другую таблицу
Допустим на склад поступает товар, пытаюсь написать триггер, который будет к имеющемуся количеству...

Триггер на изменение значения поля при добавлении записи в другую таблицу MySQL
Работаю в phpMyAdmin Ребят, такая проблема. Не понимаю в чем дело, т.к. я чайник в MySQL. В...

Нужен триггер, который будет обновлять одну таблицу при добавлении записи в другую
Нужен триггер, который срабатывает после того как добавлется запись в таблицу. Итак, имеется:...


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

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru