Форум программистов, компьютерный форум, киберфорум
C#: ASP.NET Core
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
2 / 2 / 0
Регистрация: 16.05.2024
Сообщений: 11

Как нормально сделать маппинг объектов с навигационными свойствами?

14.05.2025, 01:45. Показов 1831. Ответов 11
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Допустим у меня есть репозиторий, модель бд и бизнес модель. В репозитории мне нужно вернуть бизнес модель, не дто. Так вот, иногда мне нужно маппить навигационные сущности, иногда нужно маппить одну навигационную сущность, иногда две допустим. Как мне сделать это по-нормальному и улучшить читаемость кода ? Пробовал AutoMapper - не получилось, постоянные ошибки.


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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public class FavoritesRepository : IFavoritesRepository
{
    private readonly StreamingDbContext _context;
    
    public FavoritesRepository(StreamingDbContext context) 
    {
        _context = context;
    }
    
    public async Task<List<Favorite>> Get(long userId)
    {
        var favoriteEntities = await _context.Favorites
            .Where(f => f.UserId == userId)
            .Include(f => f.SongEntity)
            .ThenInclude(f => f.ArtistEntity)
            .Include(f => f.SongEntity)
            .ThenInclude(f => f.AlbumEntity)
            .AsNoTracking()
            .ToListAsync();
 
        var favorites = favoriteEntities
            .Select(favorite => new Favorite
            {
                Id = favorite.Id,
                UserId = favorite.UserId,
                Song = new Song
                {
                    Id = favorite.SongEntity.Id,
                    Title = favorite.SongEntity.Title,
                    AlbumId = favorite.SongEntity.AlbumId,
                    ArtistId = favorite.SongEntity.ArtistId,
                    FilePath = favorite.SongEntity.FilePath,
                    FeaturingArtists = favorite.SongEntity.FeaturingArtists,
                    IsSingle = favorite.SongEntity.IsSingle,
                    ImagePath = favorite.SongEntity.ImagePath,
                    Artist = new Artist
                    {
                        Id = favorite.SongEntity.ArtistEntity.Id,
                        Name = favorite.SongEntity.ArtistEntity.Name,
                        ImagePath = favorite.SongEntity.ArtistEntity.ImagePath
                    },
                    Album = new Album
                    {
                        Id = favorite.SongEntity.AlbumEntity.Id,
                        Title = favorite.SongEntity.AlbumEntity.Title,
                        ArtistId = favorite.SongEntity.AlbumEntity.ArtistId,
                        ImagePath = favorite.SongEntity.AlbumEntity.ImagePath,
                        ReleaseDate = favorite.SongEntity.AlbumEntity.ReleaseDate,
                    }
                }
            }).ToList();
        
        return favorites;
    }
 
    public async Task<Favorite?> Add(long userId, long songId)
    {
        var songEntity = await _context.Songs
            .FindAsync(songId);
        
        if (songEntity == null)
        {
            return null;
        }
 
        var favoriteEntity = new FavoriteEntity
        {
            UserId = userId,
            SongId = songId,
            SongEntity = songEntity
        };
        
         await _context.Favorites.AddAsync(favoriteEntity);
         await _context.SaveChangesAsync();
 
         var favorite = new Favorite
         {
             Id = favoriteEntity.Id,
             SongId = songId,
             UserId = userId,
             Song = new Song
             {
                 Id = favoriteEntity.SongEntity.Id,
                 AlbumId = favoriteEntity.SongEntity.AlbumId,
                 ArtistId = favoriteEntity.SongEntity.ArtistId,
                 Title = favoriteEntity.SongEntity.Title,
                 FilePath = favoriteEntity.SongEntity.FilePath,
                 FeaturingArtists = favoriteEntity.SongEntity.FeaturingArtists,
                 ImagePath = favoriteEntity.SongEntity.ImagePath,
                 IsSingle = favoriteEntity.SongEntity.IsSingle
             }
         };
         
         return favorite;
    }
 
    public async Task<Favorite?> Delete(long userId, long songId)
    {
        var favoriteEntity = await _context.Favorites
            .Where(a => a.UserId == userId && a.SongId == songId)
            .Include(f => f.SongEntity)
            .FirstOrDefaultAsync();
        
        if (favoriteEntity == null)
        {
            return null;
        }
        
        _context.Favorites.Remove(favoriteEntity);
        await _context.SaveChangesAsync();
 
        var favorite = new Favorite
        {
            Id = favoriteEntity.Id,
            SongId = songId,
            UserId = userId,
            Song = new Song
            {
                Id = favoriteEntity.SongEntity.Id,
                AlbumId = favoriteEntity.SongEntity.AlbumId,
                ArtistId = favoriteEntity.SongEntity.ArtistId,
                Title = favoriteEntity.SongEntity.Title,
                FilePath = favoriteEntity.SongEntity.FilePath,
                FeaturingArtists = favoriteEntity.SongEntity.FeaturingArtists,
                ImagePath = favoriteEntity.SongEntity.ImagePath,
                IsSingle = favoriteEntity.SongEntity.IsSingle
            }
        };
        
        return favorite;
    }
}


C#
1
2
3
4
5
6
7
8
9
10
11
12
public class Favorite
{
    
    public long Id { get; set; }
    
    public long UserId { get; set; }
    
    public long SongId { get; set; }
    
    public Song? Song { get; set; }
    
}


C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Song
{
    public long Id { get; set; }
    
    public long AlbumId { get; set; }
 
    public long ArtistId { get; set; }
    
    public string Title { get; set; } = string.Empty;
    
    public string FeaturingArtists { get; set; } = string.Empty;
    
    public string FilePath { get; set; } = string.Empty;
    
    public string ImagePath { get; set; } = string.Empty;
    
    public bool IsSingle  { get; set; }
    
    public Artist? Artist { get; set; }
    
    public Album? Album { get; set; }
 
}


C#
1
2
3
4
5
6
7
8
9
10
11
12
public class Artist
{
    public long Id { get; set; }
    
    public string Name { get; set; } = string.Empty;
    
    public string ImagePath { get; set; } = string.Empty;
 
    public List<Song>? Songs { get; set; } = [];
 
    public List<Album>? Albums { get; set; } = [];
}


C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Album
{
    public long Id { get; set; }
    
    public long ArtistId { get; set; }
    
    public DateTime ReleaseDate { get; set; }
    
    public string ImagePath { get; set; } = string.Empty;
    
    public string Title { get; set; } = string.Empty;
    
    public List<Song>? Songs { get; set; }
    
    public Artist? Artist { get; set; }
}
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
14.05.2025, 01:45
Ответы с готовыми решениями:

Sieve фильтрация по навигационным свойствам свойствам
Впринципе вся фильтрация у меня работала нормлаьно через Sieve до момента когда мне понадобилось...

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

маппинг на основе Code First
Всем привет, мне интересно узнать как на Code First я скажем могу пометить, что какое-то поле имеет...

11
Эксперт .NET
 Аватар для Usaga
14087 / 9305 / 1348
Регистрация: 21.01.2016
Сообщений: 34,929
14.05.2025, 05:39
Цитата Сообщение от LineHalt Посмотреть сообщение
Пробовал AutoMapper
Не надо его пробовать. Он тут ничего улучшить не может.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public async Task<List<Favorite>> Get(long userId)
    {
        var favoriteEntities = await _context.Favorites
            .Where(f => f.UserId == userId)
            .Include(f => f.SongEntity)
            .ThenInclude(f => f.ArtistEntity)
            .Include(f => f.SongEntity)
            .ThenInclude(f => f.AlbumEntity)
            .AsNoTracking()
            .ToListAsync();
 
        var favorites = favoriteEntities
            .Select(favorite => new Favorite
            {
                Id = favorite.Id,
                UserId = favorite.Us
Очень близко к правильному) На самом деле не надо выгружать модель базы, с навигационными свойствами и всякими AsNoTracking. Можно EF'у сразу сказать что и как выгрузить:

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
public async Task<List<Favorite>> Get(long userId)
    {
        return await _context.Favorites
            .Where(f => f.UserId == userId)
            .Select(favorite => new Favorite
            {
                Id = favorite.Id,
                UserId = favorite.UserId,
                Song = new Song
                {
                    Id = favorite.SongEntity.Id,
                    Title = favorite.SongEntity.Title,
                    AlbumId = favorite.SongEntity.AlbumId,
                    ArtistId = favorite.SongEntity.ArtistId,
                    FilePath = favorite.SongEntity.FilePath,
                    FeaturingArtists = favorite.SongEntity.FeaturingArtists,
                    IsSingle = favorite.SongEntity.IsSingle,
                    ImagePath = favorite.SongEntity.ImagePath,
                    Artist = new Artist
                    {
                        Id = favorite.SongEntity.ArtistEntity.Id,
                        Name = favorite.SongEntity.ArtistEntity.Name,
                        ImagePath = favorite.SongEntity.ArtistEntity.ImagePath
                    },
                    Album = new Album
                    {
                        Id = favorite.SongEntity.AlbumEntity.Id,
                        Title = favorite.SongEntity.AlbumEntity.Title,
                        ArtistId = favorite.SongEntity.AlbumEntity.ArtistId,
                        ImagePath = favorite.SongEntity.AlbumEntity.ImagePath,
                        ReleaseDate = favorite.SongEntity.AlbumEntity.ReleaseDate,
                    }
                }
            }).ToListAsync();
    }
Так и кода меньше, и код эффективнее.

Единственное, что нельзя так проецировать сущности базы на сами сущности базы. Надо бизнес-модель из DTO собирать. Но это даже лучше, на самом деле.
1
2 / 2 / 0
Регистрация: 16.05.2024
Сообщений: 11
14.05.2025, 07:48  [ТС]
Спасибо ! А как сам процесс маппинга оптимизировать, чтобы не писать постоянно стену кода ?
0
Эксперт .NET
 Аватар для Usaga
14087 / 9305 / 1348
Регистрация: 21.01.2016
Сообщений: 34,929
14.05.2025, 18:31
LineHalt, это называется не "оптимизировать", а "сократить писанину". Это не одно и то же. Меньше кода не значит быстрее.
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
16.05.2025, 06:48
Цитата Сообщение от Usaga Посмотреть сообщение
Он тут ничего улучшить не может.
Теоретически может сократить количество писанины. Но скорее всего убьет перфоманс, особенно когда нужно вычитать такие сложные объекты.

Цитата Сообщение от LineHalt Посмотреть сообщение
А как сам процесс маппинга оптимизировать, чтобы не писать постоянно стену кода ?
Автомапер или аналоги. Скорее всего не разобрались как правильно настроить профиль.
0
Эксперт .NET
 Аватар для Usaga
14087 / 9305 / 1348
Регистрация: 21.01.2016
Сообщений: 34,929
16.05.2025, 07:12
Wolfdp, не, перворманс вообще не затронет. Просто добавит сложности в проект без какого-либо профита.
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
16.05.2025, 08:19
Цитата Сообщение от Usaga Посмотреть сообщение
перворманс вообще не затронет.
Я как-то не уверен... По идеи если наваять обычный метод расширения, то это самый быстрый вариант. А в примере с жирной лямдой можно качественно вычитывать из таблицы только необходимые поля, а не грузить полную DTO.

Цитата Сообщение от Usaga Посмотреть сообщение
Просто добавит сложности в проект без какого-либо профита.
Млин, вот вроде и есть что написать как "за", так и "против". Скажу так: если с ним разобраться, то довольно прикольный инструмент и позволяет упростить некоторые моменты, в первую очередь сократить рутинную писанину. Как правило ради этого его и берут.

Но ровно также с ним можно огрести проблем, так что в каждый проект его совать явно не стоит. Особенно если между этапом "вычитал данные из БД" и "отдать пользователю модель" есть куча преобразований данных.
0
Эксперт .NET
 Аватар для Usaga
14087 / 9305 / 1348
Регистрация: 21.01.2016
Сообщений: 34,929
16.05.2025, 08:24
Цитата Сообщение от Wolfdp Посмотреть сообщение
Я как-то не уверен... По идеи если наваять обычный метод расширения, то это самый быстрый вариант. А в примере с жирной лямдой можно качественно вычитывать из таблицы только необходимые поля, а не грузить полную DTO.
Автомапер тут ничего не меняет. Он просто Expression сгенерит. А что в нём будет ты сам определяешь в профиле. МОжно и жирную лямбду сделать, можно и тощую.

Цитата Сообщение от Wolfdp Посмотреть сообщение
Скажу так: если с ним разобраться, то довольно прикольный инструмент и позволяет упростить некоторые моменты, в первую очередь сократить рутинную писанину. Как правило ради этого его и берут.
Ну это только со стороны так кажется. На моей практике оказалось, что использование автомапера занимало столько же усилий, сколько и ручное описание маперов. Только ручные маперы они вот они. А профили ещё пойди найди, где они объявлены и не являются ли составными...
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
16.05.2025, 10:23
Цитата Сообщение от Usaga Посмотреть сообщение
Автомапер тут ничего не меняет. Он просто Expression сгенерит.
Статический метод вроде всё же бодрее работает. Хотя на фоне sql запроса это конечно спички.

И разве автомапер может выдать Expression, который потом можно подсунуть тому же EF, чтобы он прям запрос сгенерил на его основе? Вроде же только нужно скормить непосредственно вычитаную dto, а значит если в ней 100500 полей, то все поля тянуть из БД, а не только нужные для конечной модели.

Цитата Сообщение от Usaga Посмотреть сообщение
А профили ещё пойди найди, где они объявлены и не являются ли составными...
Дык, неплохо бы складывать их в одном месте, а не по проекту раскидывать. Хотя конечно не так удобно, как тыкнуть конкретный метод. С поиском проблем у меня как-то не возникало, а вот с тем чтобы отслеживать актуальность всего этого добра -- это пожалуйста. Из-за того что оперируем Map<T>(object), компилятор не поймет что чего-то нехватает, либо наоборот задублировано. И хорошо если у тебя вот весь солюшен перед глазами и ты можешь проверить/поправить.
0
Эксперт .NET
 Аватар для Usaga
14087 / 9305 / 1348
Регистрация: 21.01.2016
Сообщений: 34,929
17.05.2025, 04:01
Цитата Сообщение от Wolfdp Посмотреть сообщение
Статический метод вроде всё же бодрее работает.
Без разницы. Автомапер кодогенерацией занимается. Первый обращение будет с оверхедом. Потом снегерится лямбда, по которой пройдётся JIT, и будет всё тоже самое, будто руками написано.

Цитата Сообщение от Wolfdp Посмотреть сообщение
И разве автомапер может выдать Expression, который потом можно подсунуть тому же EF, чтобы он прям запрос сгенерил на его основе?
У автомапера есть метод-расширение ProjectTo<>, который именно Expression выдаёт. А самому EF'у (как и Linq2Db) совершенно всё равно кто и как ему Expression подсунул - программист в коде или какая-то либа. А что там будет в выражении автомапером сгенеренном - определяется профилем.

Цитата Сообщение от Wolfdp Посмотреть сообщение
а значит если в ней 100500 полей, то все поля тянуть из БД, а не только нужные для конечной модели.
Это определяется профилем автомапера.

Цитата Сообщение от Wolfdp Посмотреть сообщение
Дык, неплохо бы складывать их в одном месте, а не по проекту раскидывать.
Не, вот это не проблема. Проблема - когда профили друг друга используют. А это всегда не явно. И вот это превращается в жопу полную.

Я про ситуацию:
C#
1
.ForMember(d => d.Something, opt => opt.MapFrom(c => c.Something))
Ты не можешь знать, что для Something->Something может быть (а может и не быть) другого профиля. В котором тоже может использоваться профиль. Понять полную логику проецирования в таком случае адски сложно.

Цитата Сообщение от Wolfdp Посмотреть сообщение
С поиском проблем у меня как-то не возникало
Это от размера проекта зависит. У нас довольно крупные проекты. В которых изначально затащили и активно использовали автомапер по тем же причинам, что ты озвучиваешь - "удобно".

Но с ростом кодовой базы стали замечать, что удобство мозгов снашает больше, чем проблем решает. Где-то в проецирование логики надо засунуть, которую через профиль протащить заметно сложнее, чем руками описать. Где-то инжектирование сервисов нужно, что тоже менее удобно, чем руками сделать. Где-то зависимости от других профилей неявные мешаться начали. Сначала пришли к тому, что профили руками описывали, чтобы чудес от дефолного поведения автомапера не ловить. А потом вообще начали замещать профили и руками маперы описывать. И стало реально легче. Проще, нагляднее.

Но это я так, опытом делюсь своим. Я понимаю, что если лично с проблемой не столкнёшься, то её как бы и нет, ктобы что ни говорил другое)
1
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
17.05.2025, 04:26
Цитата Сообщение от Usaga Посмотреть сообщение
У автомапера есть метод-расширение ProjectTo<>, который именно Expression выдаёт.
О, прикольно, не знал. Хотя с сложными построениями запросов в ORM и так чёрт ногу сломит, а тут ещё и в профиль засунуть чтобы верняк никто не разобрался.

Цитата Сообщение от Usaga Посмотреть сообщение
Но это я так, опытом делюсь своим. Я понимаю, что если лично с проблемой не столкнёшься, то её как бы и нет, ктобы что ни говорил другое)
Да вот как бы всё это читаю, и вроде понимаю о чём речь, а в чём проблема -- нет

Цитата Сообщение от Usaga Посмотреть сообщение
Где-то инжектирование сервисов нужно, что тоже менее удобно, чем руками сделать.
Честно говоря на мой взгляд сервисам и прочим сторонним штукам не место в профиле (а значит скорее всего отказываемся от автомапера). Это уже тянет на полноценную бизнес логику, которую бы неплохо покрывать тестами. Да и в целом видя в коде Map<MyModel>(otherModel) не особо ожидаешь что там сейчас будет происходить лютый экшен.
0
Эксперт .NET
 Аватар для Usaga
14087 / 9305 / 1348
Регистрация: 21.01.2016
Сообщений: 34,929
17.05.2025, 04:42
Цитата Сообщение от Wolfdp Посмотреть сообщение
Да вот как бы всё это читаю, и вроде понимаю о чём речь, а в чём проблема -- нет
Проблема в том, что данная библиотека не решает проблем, которые от неё ожидают)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
17.05.2025, 04:42
Помогаю со студенческими работами здесь

Проект с простыми моделями, где будет реализован маппинг предиката
Добрый вечер Хелп, не получается выполнить то что в этой ссылке приводится ...

Маппинг значений
Доброго времени суток. Вот такая есть интересная задача, надеюсь зайдет. Предположим, у нас...

ASP.NET Core API: маппинг сущность <> DTO
Добрый день! Где лучше всего осуществлять маппинг сущность &lt;&gt; DTO: 1. В репозитории 2. В...

Как просмотреть все свойства объекта.
Может кто знает как просмотреть все свойства объекта??? Например пусть есть некоторый обект ob1 =...

Как прикрутить значения веб-контролов к свойствам бизнес-объекта?
Никак не пойму, есть ли человеческий способ прикрутить значения веб-контролов к свойствам...


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

Или воспользуйтесь поиском по форуму:
12
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru