Получение данных из репозитория на сервисный уровень с помощью DTOS - PullRequest
0 голосов
/ 21 января 2019

Я нахожусь в процессе изучения шаблона репозитория ASP.NET. Я провел некоторые исследования и не смог найти правильный ответ.

У меня есть объект базы данных, сотрудник, в моем DAL, который имеет несколько столбцов. У меня есть EmployeeRepository, который отвечает за запрос данных с использованием DBContext.

Теперь у меня есть EmployeeService на моем сервисном уровне, который должен возвращать DTO для сотрудников, то есть он имеет только несколько полей (имя, фамилия, дата рождения).

Теперь, если я получу список сотрудников из EmployeeRepository как IQueryable и выберу их в качестве EmployeeDTO на моем сервисном уровне, я нарушу правило, разрешив доступ сервисного уровня к базе данных.

Если нет, EmployeeRepository вернет все данные для каждого сотрудника на сервисный уровень в виде списка, так что сервисный уровень выбирает только несколько полей для создания EmployeeDTO. Но при таком подходе я буду загружать все данные, которые мне не нужны.

Интересно, есть ли у кого-нибудь предложение.

С уважением,

Ответы [ 3 ]

0 голосов
/ 21 января 2019

Важное примечание

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

Вернуться к определению

Во-первых,давайте вернемся к определению шаблона репозитория .Если вы внимательно его прочитаете, он скажет, что его цель состоит в том, чтобы содержать логику запросов, которая в вашем сценарии выполняется с помощью LINQ-to-SQL (если вы использовали ADO.NET, вашей логикой запросов был бы фактический SQL, который вы собиралисьвыполнить).
Идея этого шаблона заключается в том, что вы не хотите, чтобы потребители вашего хранилища знали или заботились о том, какой тип механизма хранения и какие способы запросов / манипуляций вы используете.
Все, что они хотят и должнымы знаем, что у них есть интерфейс, например, такой:

public interface IEmployeeRepository
{
    IEnumerable<Employee> GetAllEmployees();
    Employee GetEmployee(int id);
}

Это означает, что если вы вводите IQueryable где-нибудь в вашем публичном интерфейсе, это автоматически говорит им: «Эй, хотя вы говорите синтерфейс, я просто дам вам знать, что это на самом деле база данных, с которой вы на самом деле разговариваете "... которая в некотором смысле превосходит цель интерфейсов и абстракций в целом.В этом отношении вы были правы, когда говорили, что уровень Service не должен иметь доступа к базе данных.

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

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

Приносить только то, что вам нужно

Теперь, когда мы установили, что IQueryable не должен быть частью общедоступного интерфейса.API, есть только одна (мне известная) альтернатива, чтобы получить то, что вам нужно: отдельный метод для каждого отдельного подмножества данных о сотрудниках, которые вам нужно получить.

Например, сценарий, который вы запросили в своемвопрос выглядел бы неправдоподобно

public interface IEmployeeRepository
{
    IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids);
}

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
}

public class EntityFrameworkEmployeeRepository : IEmployeeRepository
{
    public IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids)
    {
        using (var context = new EmployeeContext())
        {
            return context.Employees.Where(e => ids.Any(id => id == e)).ToList();
            // I haven't tested if .Any works or whether you should instead do .Join
            // but this is orthogonal to the main question
        }
    }
}

Если в будущем вам понадобится указать, скажем, только идентификатор, имя, фамилию, месячный оклад и рейтинг производительности для пары сотрудников, все, что вам понадобитсянужно создать новый DTO (например, EmployeeEvaluation), добавить метод интерфейса, возвращающий IEnumerable с заданными DTO, а затем внедрить этот новый метод в ваш EntityFrameworkRepository.

Более того, если где-то еще дальше,вы хотите получить все данные, которые у вас есть для некоторых сотрудников, ваш интерфейс может выглядетьчто-то вроде этого:

public interface IEmployeeRepository
{
    IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids);
    IEnumerable<EmployeeEvaluation> GetEmployeeEvaluations(IEnumerable<int> ids);
    IEnumerable<Employee> GetEmployees(IEnumerable<int> ids);
    IEnumerable<Employee> GetEmployees();
}


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

Под словом" клиент "я подразумеваю любой фрагмент кода, который вызывает ваш репозиторий, будь он вашим собственнымили кто-то еще. Таким образом, это может быть ваша служба, ваш контроллер, ваши тесты ИЛИ, если вы решите скомпилировать и распространить свой код как DLL, кто-то еще, кто решит его использовать.

Но, независимо от того, кто вызываетэтот код, они должны быть изолированы от знания того, что происходит за кулисами. Это также применимо, если вы владеете всей кодовой базой, как в вашем случае. Подумайте о следующем сценарии:
Когда-нибудь в будущем ваша компания решит купить какое-нибудь модное программное обеспечение, которое поможет им вести учет и ликвидировать реляционную базу данных.Чтобы ваше приложение работало, вам также придется отказаться от любого использования Entity Framework и вместо этого выполнять вызовы веб-службы для этого программного обеспечения.Теперь, если, как вы предложили, вы использовали EF напрямую во всех своих сервисах (поскольку вы не реализовали репозиторий, это единственный оставшийся вариант), на вашем слое Servuce будет много мест, где вам придетсяПерейти, удалить все ссылки на EF и преобразовать его в код, который использует новый веб-сервис.Это называется операция по дробовику , и причина в том, что вы смешиваете две вещи, которые должны быть разделены: извлечение данных и обработка данных.
Если бы вы пошли по хранилищу, единственное, что у вас было быизменить было бы хранилище (извлечение данных), тогда как вся служебная логика (обработка) осталась бы нетронутой.Прелесть развязанного кода:)

Имея это в виду, я настоятельно рекомендую вам на самом деле реализовать шаблон репозитория.Это действительно помогает вам отделить вещи в соответствии с S в SOLID.

0 голосов
/ 22 января 2019

Когда я начал учиться, у меня возникла такая же путаница.

Теперь, если я получу список сотрудников из репозитория Employee в виде IQueryable и выберу их в качестве EmployeeDTO на моем уровне обслуживания, янарушит правило, разрешив сервисному уровню доступ к базе данных.

Ваше понимание верно.Хранилище не должно предоставлять IQueryable и должно возвращать только данные.

Если нет, EmployeeRepository вернет все данные для каждого сотрудника на сервисный уровень в виде списка, поэтомучто сервисный уровень выбирает только несколько полей для создания EmployeeDTO.Но при таком подходе я буду загружать все данные, которые мне не нужны.

Это действительно основано на ваших потребностях и потребностях вашего приложения.Не существует строгого правила для объявления методов и того, как они должны возвращаться.Из следующего я мог понять две вещи: « вернуть все данные для каждого сотрудника на уровень обслуживания ».

  • Либо вы возвращаете все данные на сервисный уровень независимо от сотрудника, напр.Продукты, а затем вы получаете необходимый продукт для работника.Если это ваш случай, вы можете ограничить использование параметра
  • или иметь в виду свойства объекта, которые вы возвращаете.Это основано на DTO от хранилища до сервисного уровня.Вы можете определить свой собственный DTO в зависимости от необходимости.Для этого, я бы предложил, если бы вы могли сохранить отдельный объект для Db и DTO, тогда это усложнило бы вашу систему, и обслуживание было бы затруднено.Когда вы предоставляете свои данные пользователю, сохраняйте отдельный необходимый объект.

Пример реализации Dotnet-Repository-Pattern-Template-With-Unit-Of-Work

В этом примере использовались три слоя.

  1. Api - для предоставления данных пользователю
  2. BL - Service Layer - обрабатывать всю бизнес-логику ииметь дело с хранилищем для CRUD
  3. Постоянство - Чтобы иметь дело с постоянством - инкапсулировать большие / сложные запросы.
0 голосов
/ 21 января 2019

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

    [HttpGet("{id}")]
    public IActionResult GetCity(int id, bool includePointsOfInterest = false)
    {
        var city = _cityInfoRepository.GetCity(id, includePointsOfInterest);

        if (city == null)
        {
            return NotFound();
        }

        if (includePointsOfInterest)
        {
            var cityResult = Mapper.Map<CityDto>(city); 
            return Ok(cityResult);
        }

        var cityWithoutPointsOfInterestResult = Mapper.Map<CityWithoutPointsOfInterestDto>(city);
        return Ok(cityWithoutPointsOfInterestResult);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...