#c# #asp.net #entity-framework #entity-framework-core #aspnetboilerplate
Вопрос:
Я в процессе изучения C# и .NET и EF (с шаблоном aspnetboiler), и мне пришла в голову идея создать какой-нибудь фиктивный проект, чтобы я мог практиковаться. Но последние 4 часа я застрял с этой ошибкой и надеюсь, что кто-нибудь здесь сможет мне помочь.
То, что я создаю( ну, по крайней мере, я думаю, что создаю это правильно), — это 2 класса под названием «Ингредиент» и «Мастер».
Я хочу использовать его для классификации ингредиентов с помощью «Мастер-класса».
Например, такой ингредиент, как
- Куриная грудка
- куриная ножка
Оба они принадлежат Мясу ( ведьма вводится в базу данных «Мастер»), и вот мой код
Ингредиент.cs
public class Ingrident : Entity
{
public string Name { get; set; }
public Master Master { get; set; }
public int MasterId { get; set; }
}
Мастер.cs
public class Master : Entity
{
public string Name { get; set; }
public List<Ingrident> Ingridents { get; set; } = new();
}
IngridientAppService.cs
public List<IngridientDto> GetIngWithParent()
{
var result = _ingRepository.GetAllIncluding(x => x.Master);
//Also I try this but doesn`t work
// var result = _ingRepository.GetAll().Where(x => x.MasterId == x.Master.Id);
return ObjectMapper.Map<List<IngridientDto>>(result);
}
IngridientDto.cs
[AutoMap(typeof(IndexIngrident.Entities.Ingrident))]
public class IngridientDto : EntityDto
{
public string Name { get; set; }
public List<MasterDto> Master { get; set; }
public int MasterId { get; set; }
}
MasterDto.cs
[AutoMap(typeof(IndexIngrident.Entities.Master))]
public class MasterDto : EntityDto
{
public string Name { get; set; }
}
Когда я создавал ( для последней практики ) отношения M -> M, этот подход с .getAllIncluding работал, но теперь, когда у меня есть Один ->> Много, это не сработает.
Надеюсь, кто-нибудь сможет мне помочь или, по крайней мере, дать какой-нибудь хороший намек.
Хорошего дня!
Комментарии:
1. Создайте повторный проект на GitHub, который разветвляется от шаблона aspnetboilerplate/module-zero-core-шаблона .
Ответ №1:
Прямо скажем, примеры, на которые вы, вероятно, ссылаетесь (в отношении репозитория и т. Д.), Чрезмерно сложны и в большинстве случаев не то, что вы хотели бы реализовать.
Первая проблема, которую я вижу, заключается в том, что, хотя ваши сущности настроены на соотношение «1 ко многим» от Мастера к ингредиентам, ваши DTO настроены от ингредиента к мастерам, что определенно не будет отображаться должным образом.
Начните с самого простого. Избавьтесь от репозитория и избавьтесь от DTO. Я не уверен, что делает базовый класс «Сущность», но я предполагаю, что он предоставляет общее ключевое свойство, называемое «Идентификатор». Для начала я бы, наверное, тоже отказался от этого. Когда дело доходит до первичных ключей, обычно существует два подхода к именованию: каждая таблица использует PK, называемый «Id», или каждая таблица использует PK с именем таблицы, суффиксом «Id». Т. е. «Id» против «Ингредиент». Лично я нахожу, что второй вариант делает это очень ясным при соединении FKs и PKs, учитывая, что у них будет одно и то же имя.
Когда дело доходит до представления отношений с помощью свойств навигации, одной важной деталью является обеспечение связи свойств навигации с их соответствующими свойствами FK, если таковые имеются, или лучше использовать свойства тени для FKs.
Например, с вашей таблицей ингредиентов, избавляясь от базового класса сущности:
[Table("Ingredients")]
public class Ingredient : Entity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int IngredientId { get; set; }
public string Name { get; set; }
public int MasterId { get; set; }
[ForeignKey("MasterId")]
public virtual Master Master { get; set; }
}
В этом примере используются атрибуты EF, чтобы помочь EF определить, как разрешить свойства сущности для соответствующих таблиц и столбцов, а также взаимосвязь между Ингредиентом и Основным. EF может многое из этого решить по соглашению, но полезно понимать и применять его явно, потому что в конечном итоге вы столкнетесь с ситуациями, когда соглашение работает не так, как вы ожидаете.
Идентификация (первичного)ключа и указание на то, что это столбец идентификаторов, также указывает EF на то, что база данных автоматически заполнит PK. (Настоятельно рекомендуется)
Со стороны Мастера мы делаем что-то подобное:
[Table("Masters")]
public class Master : Entity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MasterId { get; set; }
public string Name { get; set; }
[InverseProperty("Master")]
public virtual ICollection<Ingredient> Ingredients { get; set; } = new List<Ingredient>();
}
Снова мы обозначаем Первичный ключ, и для нашей коллекции ингредиентов мы сообщаем EF, какое свойство на другой стороне (Ингредиент) он должен использовать, чтобы связать со списком ингредиентов этого Мастера с помощью InverseProperty
атрибута.
Атрибуты-это всего лишь один из вариантов настройки отношений и т. Д. Другие варианты-использовать реализующие классы конфигурации IEntityConfiguration<TEntity>
(ядро EF) или настроить их как часть OnModelCreating
события в DbContext. Этот последний вариант я бы рекомендовал только для очень небольших проектов, так как он может быстро начать становиться чем-то вроде метода Бога. Вы можете разделить его на вызовы различных частных методов, но тогда вы можете просто использовать IEntityConfiguration
классы.
Теперь, когда вы идете за ингредиентами с его Мастером, или Мастером с его ингредиентами:
using (var context = new AppDbContext())
{
var ingredients = context.Ingredients
.Include(x => x.Master)
.Where(x => x.Master.Name.Contains("chicken"))
.ToList();
// or
var masters = context.Master
.Include(x => x.Ingredients)
.Where(x => x.Name.Contains("chicken"))
.ToList();
// ...
}
Шаблоны репозиториев-это более продвинутая концепция, для реализации которой есть несколько веских причин, но по большей части они не являются необходимыми и являются анти-шаблоном в реализациях EF. Я считаю, что общие репозитории всегда являются анти-шаблоном для реализаций EF. Т. е. Repository<Ingredient>
Главная причина не использование репозиториев, особенно общих репозиториев с EF, означает, что вы автоматически повышаете сложность своей реализации и/или ограничиваете возможности, которые EF может привнести в ваше решение. Как вы видите из своего примера, простое прохождение желаемой нагрузки через репозиторий означает запись в сложных Expression<Func<TEntity>>
параметрах, и это просто покрывает желаемую загрузку. Поддержка проецирования, разбивки на страницы, сортировки и т. Д. Добавляет еще больше сложности котельной плите или ограничивает ваше решение и производительность без этих возможностей, которые EF может предоставить сразу.
Некоторые веские причины рассмотреть возможность изучения реализаций репозиториев /w EF:
- Облегчите модульное тестирование. (Репозитории легче имитировать, чем DbContexts/наборы баз данных)
- Централизация правил данных низкого уровня, таких как аренда, мягкое удаление и авторизация.
Некоторые плохие (хотя и очень распространенные) причины для рассмотрения репозиториев:
- Абстрагирование кода от ссылок или знаний о зависимости от EF.
- Абстрагирование кода, чтобы можно было заменить EF.
Проектирование в DTO или ViewModels является важным аспектом создания эффективных и безопасных решений с помощью EF. Неясно, что такое «ObjectMapper», является ли это экземпляром автоматического отображения или чем-то еще. Я бы настоятельно рекомендовал начать понимать проекцию с помощью Select
синтаксиса Linq для заполнения желаемого DTO из моделей. Первое ключевое отличие при правильном использовании проекции заключается в том, что при проектировании графа объектов вам не нужно беспокоиться о быстрой загрузке связанных объектов. Любая связанная сущность / свойство, на которые ссылается ваша проекция ( Select
), будет автоматически загружена по мере необходимости. Позже, если вы захотите использовать такой инструмент, как Automapper, чтобы помочь устранить беспорядок Select
операторов, вам потребуется настроить конфигурацию сопоставления, а затем использовать ProjectTo
метод Automapper, а не Map
. ProjectTo
работает с IQueryable
реализацией EF, чтобы разрешить ваше сопоставление с SQL точно так же, как Select
это делает, где Map
нужно будет вернуть все загруженное, чтобы заполнить соответствующие данные. ProjectTo
и Select
может привести к более эффективным запросам, которые могут лучше использовать преимущества индексации, чем нетерпеливая загрузка целых графов объектов. (Меньше данных по проводу между базой данных и сервером/приложением) Map
по-прежнему очень полезно, например, в сценариях, когда вы хотите скопировать значения обратно из DTO в загруженный объект.
Ответ №2:
Сделай это вот так
public class Ingrident:Entity
{
public string Name { get; set; }
[ForeignKey(nameof(MasterId))]
public Master Master { get; set; }
public int MasterId { get; set; }
}