Подтвердить что ты не робот

Entity Framework, свойства навигации и шаблон хранилища

Я изо всех сил пытаюсь понять идеальную реализацию Entity Framework и шаблона репозитория. Я использую Entity Framework 4.3 с кодовым кодом, и я просто не могу заставить себя задуматься над правильным использованием структуры сущности.

Мне нравятся вещи, которые EF приносит в таблицу, такие как отслеживаемые объекты, ленивая загрузка, свойства навигации и т.д. Но некоторые из них не очень хорошо сочетаются с шаблоном репозитория, насколько я понимаю. Давайте посмотрим на несколько примеров, и, возможно, вы, ребята, можете установить меня прямо.

Generic Repository vs Non-generic Repository

Мое первоначальное впечатление от общего репозитория заключается в том, что мне это не нравится, потому что мне не нужна точно такая же функциональность для каждого объекта. Например, у меня есть репозиторий, в котором хранятся простые переменные (пары ключ/значение) в базе данных. Мне не нужен метод Add или Delete, потому что это статические переменные. Мне нужен только метод Update и метод Get. Общий репозиторий просто не кажется очень надежным и не позволяет использовать много настраиваемого кода в слое данных. Я также ненавижу, когда общие репозитории возвращают IQueryable<T>, потому что он дает верхним уровням возможность писать выражения непосредственно против хранилища данных, а верхние слои должны предполагать, что используемая технология доступа к данным правильно реализует IQueryable, чтобы она запрашивала базу данных и не вынимая все в память и не запрашивая ее оттуда.

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

Свойства навигации

Мне нравится концепция свойств навигации, но мне кажется, что я редко использую их при реализации шаблона репозитория. Например, у меня есть пользователь с навигационным свойством, называемым "Псевдонимы". Если я хочу добавить псевдоним для пользователя, было бы легко добавить его через свойство навигации.

myUser.Aliases.Add(new Alias { Name="cls", Value="ClearScreen" });

Но тогда, где я называю dbContext.SaveChanges()? Я передал мне myUser, и я использовал свойство навигации, чтобы не вводить мой IAliasRepository в класс, в котором я есть. Однако теперь у меня нет способа сохранить новый псевдоним базы данных, потому что мои верхние слои не знают о Entity Framework. Теперь мне все равно нужно ввести IAliasRepository, чтобы я мог все _aliasRepository.SaveChanges(). Ну, теперь это похоже на полную трату. Я чувствую, что должен был просто использовать _aliasRepository.AddAlias(newAlias), потому что я должен все равно добавить репозиторий.

Self-Tracking Entities

Объекты самоконтроля выглядят потрясающе, но они не подходят для приложений, где вы пытаетесь скрыть данные уровня доступа к данным из остальной части приложения. Например, если бы я писал репозитории и не знал, что они будут использовать EF, я бы определенно добавил метод Update(Entity entity). Однако в EF вам не нужно это делать, потому что вы можете просто внести изменения в объект, а затем вызвать SaveChanges(). Сущность отслеживает все измененные и сохраняет эти изменения в базе данных.

var myEntity = _entityRepository.GetEntity("some unique ID");
myEntity.SomeProperty = "new value";
_entityRepository.SaveChanges();

Это заставляет меня исключать мои методы обновления, которые я бы включил, если бы я не знал, что EF им не нужны. Это делает рефакторинг более сложным по дороге, потому что мне, возможно, придется вернуться и добавить надлежащие методы обновления. Моим единственным другим вариантом было бы включить методы в любом случае, а затем просто ничего не делать с ними при реализации моих репозиториев.

public void UpdateEntity(Entity entity)
{
    // Do nothing. EF is tracking changes and they will be persisted when
    // SaveChanges() is called.
}

Итак, мой код будет выглядеть так, даже если он совершенно не нужен.

var myEntity = _entityRepository.GetEntity("some unique ID");
myEntity.SomeProperty = "new value";
_entityRepository.UpdateEntity(myEntity);
_entityRepository.SaveChanges();

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

Сохранение синхронизации DbContext

Еще одна странная причуда этого шаблона заключается в том, что вы должны быть очень осторожны с вашим DbContext. Один и тот же экземпляр должен быть введен во все репозитории. В противном случае, если вы вытаскиваете объекты из одного репозитория и пытаетесь связать их с объектами из другого репозитория, тогда они не будут играть вместе, потому что они из разных экземпляров DbContext. Контейнеры IoC упрощают управление, но это странная проблема для разработчиков, начиная с EF. На самом деле проблема не такая, как просто еще одна странность с Entity Framework и шаблоном репозитория.

Какова правильная реализация шаблона репозитория с помощью EF? Как преодолеть эти препятствия?

4b9b3361

Ответ 1

Общий репозиторий против не общего репозитория

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

Свойства навигации

Сам репозиторий должен использоваться с общим корнем. Совокупный корень - это совокупность нескольких связанных объектов, где у вас есть репозиторий только для принципала, потому что зависимости не могут существовать без родителя. Репозиторий сам обрабатывает загрузку и сохранение всех типов сущностей в агрегации.

Даже с совокупными корнями вы столкнетесь с некоторыми проблемами. Например, как обрабатывать отношения "многие ко многим"? Отношение многих ко многим всегда представляет собой сценарий, в котором нет реального основного или зависимого объекта = они не находятся в агрегировании. Если вы только установили связь между этими двумя объектами с помощью свойства навигации, все еще хорошо, но EF также позволяет создавать связанный объект с помощью свойства навигации, и это как-то нарушает цель репозитория. Вы можете принудительно использовать свой репозиторий для проверки существования отношений, но это может вызвать множество дополнительных запросов, поэтому вы, скорее всего, оставите его как пропущенную абстракцию вашей реализации.

Объекты самоконтроля

Из вашего кода я думаю, что вы смутили объекты самоконтроля со связанными объектами. То, что вы описываете, - это разница между прикрепленными и отдельными объектами. Если вы хотите поддерживать оба сценария в одном коде, ваш метод UpdateEntity имеет значение, потому что вы должны проверить, прикреплен ли объект и прикрепить его + установить состояние, если нет.

Сохранение синхронизации DbContext

Здесь вам не хватает единицы работы. Сам репозиторий обертывает только запросы и сохраняет сущности в контексте, но блок работы обрабатывает создание /retireval контекста и сохраняющиеся изменения в базе данных. В качестве примера вы можете взять DbSet (реализация репозитория EF) и DbContext (реализация EF единицы работы).