Форум программистов, компьютерный форум, киберфорум
C#: Базы данных
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.57/7: Рейтинг темы: голосов - 7, средняя оценка - 4.57
5 / 5 / 2
Регистрация: 21.07.2016
Сообщений: 34

Отношение многие-ко-многим в EF core 6+

03.08.2023, 15:51. Показов 2003. Ответов 22
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Необходимо создать отношение многие-ко-многим на основе классов (code first), но с небольшим нюансом (см. ниже). Делаю это так:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class University {
    public int Id { get; init; }
}
 
public class Teacher {
    public int Id { get; init; }
    public University University { get; set; } // препод закреплен за одним единственным университетом
    public List<Subject> Subjects { get; } = new(); // препод может преподавать несколько предметов, а не только один
}
 
public class Subject {
    public int Id { get; init; }
    public List<Teacher> Teachers { get; } = new(); // предмет НЕ должен быть привязан к преподам! данная строка нужна лишь для создания отношения многие-ко-многим
}
На уровне БД все ок - создается промежуточная таблица TeacherSubject, а сами таблицы Teacher и Subject ничего не знают друг о друге. Но вот на уровне классов мне нужно избавиться от связи Subject с коллекцией Teachers. Предмет ничего не должен знать о преподавателях. Список предметов универсальный для всего мира, и надобности хранить у каждого предмета инфу обо всех преподах мира, которые его преподают, нет. А вот преподаватель ничего не значит без предметов, которые он преподает. Поэтому коллекция teacher.Subjects имеет место быть.

Пробовал что-то исправить через fluent-api, но .HasMany().WithMany() особо ничем не помогут, т.к. все равно требуют наличия свойств-коллекций у классов. Можно конечно самому создать класс для описания промежуточной таблицы, но это прям совсем ручная работа и не только на уровне описания БД, но и на уровне дальнейшего использования такого класса.

Есть ли какие-то еще способы, кроме "задепрекейть и просто не используй subject.Teachers в своем коде"?
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
03.08.2023, 15:51
Ответы с готовыми решениями:

BindingSource и DataSet и отношение многие-ко-многим
в БД имеем: таблицу f, s и для отображения МкМ fs. две связи(Relations) f-&gt;fs и s-&gt;fs DataTable f = new DataTable(&quot;f&quot;);...

Отношение Многие ко Многим
Добрый день, недавно начал осваивать Асп.нет, и хотелось бы реализовать связь многие ко многим. Делаю все согласно примером найденным в...

Отношение многие-ко-многим ASP.NET MVC
Доброго времени суток, коллеги. Возникла ситуация, когда мне необходимо установить отношение многие ко многим. Опишу сразу на примере:...

22
403 / 265 / 69
Регистрация: 12.04.2020
Сообщений: 1,404
07.08.2023, 18:47
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от JavaFX2 Посмотреть сообщение
На уровне БД все ок - создается промежуточная таблица TeacherSubject
даже разбираться лень
возьмите и создайте сами
а не надейтесь на EF
0
5 / 5 / 2
Регистрация: 21.07.2016
Сообщений: 34
07.08.2023, 23:44  [ТС]
Цитата Сообщение от Dr9vik Посмотреть сообщение
даже разбираться лень
Очень конструктивный ответ. А главное какой полезный. Когда обсуждение уже давно переехало немного в другое русло.

Цитата Сообщение от Usaga Посмотреть сообщение
EF должен всё выражение целиком видеть... разбирает этот AST и ...
Очередной раз спасибо за развернутый ответ! Именно за подобными ответами, помогающими упорядочить знания по полочкам, и обращаешься на форум.

Позже для себя проведу сравнительный тест с конструктором и паблик-полями.

Цитата Сообщение от Usaga Посмотреть сообщение
я им уже не пользуюсь и пользоваться не буду
Можешь сказать почему? И какие альтернативы используешь? Linq2Db? (Если его, можешь описать в двух словах самые критические отличия, которые превалируют у Linq2Db по отношению к EF? Или дать ссылку, где именно они указаны. С Linq2Db тоже приходилось сталкиваться на одном проекте, но тогда я не акцентировал на нем внимание)
0
Эксперт .NET
 Аватар для Usaga
14367 / 9468 / 1360
Регистрация: 21.01.2016
Сообщений: 35,721
08.08.2023, 02:28
JavaFX2, из-за сильной ограниченности EF'а в возможностях. Работая над очень крупным проектом мы пришли к тому, что не можем реализовывать эффективно критичные к производительности запросы. Просто никак. И речь не о том, что мы просто огульно рукой махнули на EF: "А, говно!". Мы запросы очень долго тюнили, разделяли на несколько, переписывали по-разному. И всё это с профилировщиком в руках. Т.е мы смотрели на результирующий текст SQL-запроса, план, который СУБД по нему строит, время, которое уходило у EF'а на генерацию этого запроса, время, которое EF тратил на материализацию.

И в конечном итоге пришли к тому, что такие критичные места проще реализовать на чистом SQL. Но тут же встал вопрос: а на кой черт нам в проекте нужна такая ORM, которая при хоть сколько-то сложных запросах сразу сдувается? И мы решили притянуть в проект (параллельно с EF, который сразу стал легаси) Linq2Db. И все сложные запросы, которые на тот момент требовали тонкой настройки, я успешно перевёл на Linq2Db дня за три. И они начали "летать" просто.

Ключевые особенности Linq2Db, которые не просто есть, а которые ОЧЕНЬ широко используем в проекте: пользовательские функции в запросах и предикатах, пользовательские выражения в запросах и предикатах (появилась возможность работать с Full Text Search на раз-два), batch update\delete, MERGE (а это вообще бомба!), CTE, рекурсивные CTE, INSERT\MERGE WITH OUTPUT, кастомные условия JOIN'а таблиц (наверное, это больше для легаси баз, но у нас именно такая), кастомные маперы\конвертеры, возможность обращаться к произвольным полям в запросе, даже сменные маппинги (не знаю уж насколько это надо, но есть), отсутствие change tracker'а и identity map'ы, query hint'ы, временные таблицы и прочее...

Там реально много всего, что внезапно становится нужно, когда оно появляется в арсенале ORM. А EF... Он сильно абстрактный. Авторы этой ORM умышленно старались его сделать таким, чтобы был "подальше" от СУБД. Поэтому ты теряешь кучу возможностей, которые в реальном проекте внатуре нужны. Чтобы быстро всё работало.

Вот куски запросов и прочих вещей из реальных проектов, где Linq2Db используется у нас:

Проекция с вложенными коллекциями. Здесь Linq2Db сам разобъёт запрос на три и корректно в мапяти смёржит. EF только недавно так научился делать.
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        var mediaItemCategory = await Context.GetTable<MediaItemCategory>()
            .Where(x => x.Id == mediaItemCategoryId && x.AgencyId == agencyId)
            .Select(x => new MediaItemCategoryEditReadModel
            {
                Id                   = x.Id,
                Name                 = x.Name,
                UserGroupIds         = x.UserGroups.Select(y => y.UserGroupId).ToList(),
                PublicApiCategoryIds = x.PublicApiCategories.Select(y => y.PublicApiCategoryId).ToList(),
                Retention            = new MediaItemCategoryRetentionEditReadModel
                {
                    KeepForDays   = x.RetentionKeepForDays,
                    KeepForMonths = x.RetentionKeepForMonths,
                    KeepForYears  = x.RetentionKeepForYears
                }
            })
            .FirstOrDefaultAsync();


Вставляем запись только с перечисленными полями. И сразу получаем на руки полученный Primary Key.
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
        var categoryId = (Guid)await Context.GetTable<MediaItemCategory>()
            .InsertWithIdentityAsync(() => new MediaItemCategory
            {
                AgencyId               = agencyId,
                Name                   = model.Name,
                CreatedAt              = model.CreatedAt,
                RetentionKeepForYears  = model.Retention!.KeepForYears,
                RetentionKeepForMonths = model.Retention!.KeepForMonths,
                RetentionKeepForDays   = model.Retention!.KeepForDays
            });


Обновляем указанные поля.
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
        await Context.GetTable<MediaItemCategory>()
            .Where(x => x.Id == model.Id && x.AgencyId == agencyId)
            .UpdateAsync(_ => new MediaItemCategory
            {
                Name                   = model.Name,
                RetentionKeepForYears  = model.Retention!.KeepForYears,
                RetentionKeepForMonths = model.Retention!.KeepForMonths,
                RetentionKeepForDays   = model.Retention!.KeepForDays
            });


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

C#
1
2
3
4
5
6
7
        await Context.GetTable<MediaItemCategory>()
            .Where(x => x.Id == model.Id && x.AgencyId == agencyId)
            .UpdateAsync(x => new MediaItemCategory
            {
                Name                   = model.Name,
                RetentionKeepForYears  = model.Retention!.KeepForYears + x.RetentionKeepForYears
            });


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

C#
1
2
3
4
5
6
        await Context.GetTable<MediaItemCategoryUserGroup>()
            .BulkCopyAsync(userGroupIds.Select(x => new MediaItemCategoryUserGroup()
            {
                CategoryId  = mediaItemCategoryId,
                UserGroupId = x
            }));


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

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
                .Merge()
                .Using(new []
                {
                    new 
                    {
                        MediaItemId = mediaItemId,
                        CategoryId  = mediaItemCategoryId,
                    }
                })
                .On((target, source) => 
                    source.MediaItemId == target.MediaItemId 
                    && source.CategoryId == target.CategoryId )
                .InsertWhenNotMatched(source => new MediaItemToMediaItemCategory
                {
                    MediaItemId = source.MediaItemId,
                    CategoryId  = source.CategoryId,
                })
                .MergeAsync();


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

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
            Context.GetTable<K9Weather>()
                .Merge()
                .Using(request.WeatherIds.Select((weatherId) =>
                    new K9Weather
                    {
                        K9Id = k9Id,
                        K9WeatherId           = weatherId
                    }))
                .On((source, target) => source.K9Id == target.K9Id &&
                                        source.K9WeatherId == target.K9WeatherId)
                .InsertWhenNotMatched()
                .DeleteWhenNotMatchedBySourceAnd(x => x.K9Id == k9Id)
                .Merge();


Пользовательские функции в запросах.
Кликните здесь для просмотра всего текста

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
// Вызовы пользовательских функций
    internal static class Liqn2DbSqlServerFunctions
    {
        [Sql.Extension("dbo.GetFullPersonName({lastName}, {firstName}, {midName})", IsPredicate = true, ServerSideOnly = true)]
        public static string GetFullPersonName(this ISqlServerExtensions ext, [ExprParameter] string lastName, [ExprParameter] string firstName, [ExprParameter] string midName)
        {
            throw new NotSupportedException();
        }
 
        [Sql.Extension("dbo.GetAge({dob}, {date})", IsPredicate = true)]
        public static int? GetAge(this ISqlServerExtensions ext, [ExprParameter] DateTime? dob, [ExprParameter] DateTime? date)
        {
            if (dob is null || date is null)
            {
                return default;
            }
 
            return dob.Value.GetAge(date.Value);
        }
// А тут вообще оператор СУБД можно вызвать. Любой.
        [Sql.Extension("COALESCE(NULLIF({first}, ''), NULLIF({second}, ''))", IsPredicate = true)]
        public static string FirstNonEmpty(this ISqlServerExtensions ext, [ExprParameter] string first, [ExprParameter] string second)
        {
            return first.ValueOrSubstitution(second);
        }
   }
 
               var nameModel = Context.GetTable<InvolvedPerson>()
                .Where(x => x.Id == caseLogNameId)
                .Select(x =>
                    new InvolvedPersonReportModel()
                    {
                        Involvement         = x.Involvement.Code,
                        NameType            = x.NameType.Description,
                        Name                = ext.GetFullPersonName(x.Person.LastName, x.Person.FirstName, x.Person.MidName),
                        DOB                 = x.Person.DOB,
                        Age                 = ext.GetAge(x.Person.DOB, x.Incident.ReportedDateTime ?? now),
                        Race                = x.Person.Race.Code,
                        DriverLicense       = x.Person.DriverLicense + " " + x.Person.DriverLicenseState.Code,
                    })
                .FirstOrDefault();
 
// Ниже функции для работы с криптографией в базе
 
internal static class DbCryptoFunctions
{
    [Sql.Extension("crypt({what}, gen_salt('bf'))", IsPredicate = true, ServerSideOnly = true)]
    public static string EncryptWithGeneratedSalt(this ISqlExtension ext, [ExprParameter] string what)
    {
        throw new NotSupportedException();
    }
 
    [Sql.Extension("crypt({what}, {salt})", IsPredicate = true, ServerSideOnly = true)]
    public static string EncryptWithSalt(this ISqlExtension ext, [ExprParameter] string what, [ExprParameter] string salt)
    {
        throw new NotSupportedException();
    }
}
 
    public async Task Create(UserCreateModel model)
    {
        var userId = (Guid)await Context.GetTable<User>() // User - сущность, и она сильно не похожа на DTO
            .InsertWithIdentityAsync(() => new User
            {
                FirstName     = model.FirstName,
                LastName      = model.LastName,
                Login         = model.Login,
                ExternalLogin = model.ExternalLogin,
                BadgeId       = model.BadgeId,
                Email         = model.Email,
                RoleId        = model.UserRoleId,
                IsActive      = model.IsActive,
                CreatedAt     = model.CreatedAt,
                ModifiedAt    = model.ModifiedAt,
                Password      = Sql.Ext!.EncryptWithGeneratedSalt(model.Password)
            });
 
        await AddUserGroupUsers(userId, model.UserGroupIds); // Отдельно сохраняем группы
    }


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

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
        var publicApiCategoriesPlain = publicApiCategories.SelectMany(a => a.Values, 
            (a, b) => new
        {
            Name = a.Name.ToLower(),
            Value = b.ToLower() 
        }).ToList();
 
        return await Context.GetTable<MediaItemCategoryPublicApiCategory>()
            .Where(x => x.Category.AgencyId == agencyId)
            .Where(mic2pac => 
                publicApiCategoriesPlain.Any(pac => 
                    Sql.AsNotNull(pac.Name) == mic2pac.PublicApiCategory.ApiName.ToLower()
                    && mic2pac.PublicApiCategory.Values.Select(mic2pacv =>mic2pacv.Value)
                        .Any(mic2pacv => mic2pacv.ToLower() == Sql.AsNotNull(pac.Value)))
            )
            .Select(x => x.CategoryId)
            .Distinct()
            .ToListAsync();
 
// Под виртуальной таблицей тут имеется в виду такая конструкция, которая будет собрана из коллекции
    SELECT
      *
    FROM
      (VALUES
        ('test','qer'), ('test','qwerqwr'), ('other','uioiuy'),
        ('other','mnnmh')
      ) pac("Name", "Value")


Ну и тому подобное. Таких примеров можно тонну насобирать. Идея в том, что Linq2Db на голову богаче на возможности, чем EF. Что позволяет тебе писать хорошие, гибкие и производительные запросы в серьёзных проектах. А не просто поиграться...

Если ты только начинаешь изучать ORM, то Linq2Db однозначно рекомендую. Даже, если в проекте, куда ты попадёшь, будет EF, после знакомства с Linq2Db ты лучше начнёшь понимать как всё это работает. Ну а если начинаешь новый проект, то смотреть на EF вообще смысла нет.

А если у тебя в проекте будет не древний EF6, а EF Core, то используя адаптер ты можешь переиспользовать мапинги EF'а в связке с Linq2Db.
3
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
08.08.2023, 02:28

Модель. Отношение многие ко многим, и многие ко всем?
Добрый день, уважаемые форумчане! Имеется таблица модель: class Templates(models.Model): template = models.TextField() ...

Отношение многие ко многим
Здравствуйте! Есть задача Есть две сущности АВТОРЫ и КНИГИ, связь М-М (многие к многим). Создайте структуру таблиц для этих сущностей и...

Отношение многие ко многим(Теги к новостям)
В общем, есть статьи, у каждой статьи - несколько тегов. У тега может быть несколько статей. Я знаю, как сделать выборку статей, но не знаю...

Реализация программы по управлению таблицами (отношение многие-ко-многим)
Здравствуйте! Имеется схема БД, состоящая из трёх таблиц: Первая таблица (рабочее место пользователя) Rab с полями: 1. kod_comp...

Как реализовать отношение "Многие-ко-многим" с помощью Мар?
Подскажите как реализовать отношение &quot;Многие - ко - многим&quot; с помощью Мар? Если я реализовываю класс Person в котором хранится имя и...


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

Или воспользуйтесь поиском по форуму:
23
Ответ Создать тему
Новые блоги и статьи
GitDOS: Когда я решил скрестить MS-DOS, GitHub и Claude
MaGz GoLd 08.07.2026
Привет, Киберфорум! Хочу поделиться своим проектом, который, надеюсь, вас заинтересует. GitDOS — это веб-приложение, которое позволяет запускать DOS в браузере с тремя крутыми фичами: диски живут в. . .
Учебный мини "цифровой двойник" = (Сервер = Java + Flask_питон )+(3D = Гугловскаая 4T nvideo + google colab) +( WMS = Odoo на Docker)
anaschu 07.07.2026
Увидел в требованиях к вакансии создателя имитационных моделей связку (REst + Api + WMS + Anylogic + питон + 3D Nvideo), и решил посмотреть, что это такое. Из этого всего я работал более менее. . .
Море в объективе
kumehtar 06.07.2026
Хотел написать много проникновенных слов, но передумал. Оно само - говорит. Кто слышит тот слышит.
Doom для терминала без стрельбы и монстров. 3D Raycasting на ascii.
dcc0 05.07.2026
Попросил нейронную сеть deepai. org написать рейкастинг 3D с библиотекой ncurses для Linux. Чтобы можно было ходить на стрелочки. Чтобы стены были отрисованы символами. Справилась. Первый вариант. . .
Установка статуса документа по условию
Maks 05.07.2026
Алгоритм из решения ниже реализован на нетиповом документе "НарядПутевка" разработанного в КА2. Задача: в табличной части "Материалы" документа при записи автоматически устанавливать статус. . .
Сезонность и суточность закисления почв
anaschu 04.07.2026
200 часов это все равно моловато. Есть ситуации, но нестандартные, когда смена происходит за 5 лет. Но обычно это 50 лет и более. Наверное, закисление почвы происходит сезонно в средней. . .
В чем ценность человеческого опыта в глобальном смысле?
kumehtar 03.07.2026
Возможно, ценность человека не в том, что он однажды достигает мудрости, а в том, что он становится носителем карты пути. Он знает не только истину, но и последовательность внутренних изменений,. . .
интеграция AnyLogic с самописным REST API и переход на Odoo
anaschu 03.07.2026
Успешная интеграция AnyLogic с самописным REST API и переход на промышленную Odoo WMS Сегодня проделал огромный путь от простой симуляции физических процессов до построения полноценной. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru