Как вы управляете глубокими реляционными деревьями в Entity Framework?


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

Затем я думаю о том, какая стратегия является наилучшей, чтобы справиться с этой ситуацией. Я не могу допустить, чтобы при извлечении сущности EF разрешал все зависимости дерево, так как это закончится большим количеством бесполезных соединений (бесполезно, потому что, возможно, мне не нужны эти данные на следующем уровне).
  • Если я отключаю lazy loading и принудительно выполняю нетерпеливую загрузку для того, что необходимо, он работает как ожидалось, но если другой разработчик вызывает child.Parent.Id вместо child.ParentId, пытаясь сделать что-то новое (например, новое требование или функцию, не рассматриваемую в начале), он получит NullReferenceException, Если эта зависимость не была включена, что плохо... но это будет ... "быстрая ошибка", и ее можно было исправить сразу.

  • Если я включу отложенную загрузку , то обращение к child.Parent.Id вместо child.ParentId будет заканчиваться автономным запросом к БД при каждом обращении к ней. Он не подведет, но это еще хуже, потому что ошибки нет, только снижение производительности, и весь код должен быть пересмотрен.

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

  • Я не доволен тем, что позволяю EF выполнять произвольные запросы к БД в любой момент. Я хотел бы получить всю информацию в одном кадре, если это возможно.

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

  • Я могу создать класс EntityBase, который содержит данные в таблица без коллекций, поэтому к ним нельзя получить доступ. И конкретные реализации, которые содержат отношения, проблема в том, что у вас нет большой гибкости, так как C# не допускает множественного наследования.

  • Я могу создавать интерфейсы, которые "маскируют" объекты, скрывая свойства, недоступные при вызове этого метода. Например, если у меня есть свойство User.Roles, для того чтобы показать сетку будут все пользователи, мне не нужно разрешать свойство .Roles, поэтому я мог бы создать интерфейс 'IUserData', который не содержит такого свойства.

Но я не думаю, что если эта дополнительная работа стоит, возможно, быстрого NullReferenceException Указания "это свойство не было загружено" будет достаточно.

Можно ли создать определенный тип исключения, если свойство является виртуальным и оно не было переопределено/установлено ?

Какой метод вы используете?

Спасибо.

1 2

1 ответ:

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

Если разработчик использует user.MiddleName.Trim() и MiddleName является null, он получает NullReferenceException и сделал что-то неправильно, либо не проверил null, либо не убедился, что MiddleName установлено значение. То же самое когда он обращается к user.Roles и получает NullReferenceException: он не проверил null или не вызвал соответствующий метод вашего API, который загружает Roles пользователя.

Я бы сказал: объясните, как работают свойства навигации и что они должны запрашиваться явно, и пусть приложение аварийно завершит работу, если разработчик не соблюдает правила. Он должен понять ошибку и исправить ее.

В качестве помощи вы можете сделать загрузку связанных данных явной каким-либо образом в API, например с помощью методов например:

public User GetUser(int userId);
public User GetUserWithRoles(int userId);

Или:

public User GetUser(int userId, params Expression<Func<User,object>>[] includes);

Который можно было бы назвать с:

var userWithoutRoles = layer.GetUser(1);
var userWithRoles = layer.GetUser(2, u => u.Roles);

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

Два дополнительных замечания:

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

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

Я хотел бы получить всю информацию в одном кадре, если это возможно.

Многократные запросы не обязательно приводят к худшей производительности, чем один запрос с большим количеством Includes. На самом деле сложная нетерпеливая загрузка может привести к умножению данных на проводе и сделать материализацию сущностей очень сложной. занимает много времени и медленнее, чем несколько ленивых или явных запросов загрузки. (вот пример, где производительность запроса была улучшена в 50 раз, изменив его с одного запроса с Includes на более чем 1000 запросов без Include.) Квинтэссенция такова: вы не можете достоверно предсказать, какая стратегия загрузки лучше в конкретной ситуации, не измерив производительность (если производительность имеет значение в этой ситуации).