Если ваш уровень Сервисов будет содержать вашу логику c и взаимодействовать с вашими сущностями, а ваши MVC Контроллеры в основном anemi c, проходящие через Сервисы, похоже, что вы хотите, чтобы ваши Сервисы возвращались Модели (DTO или ViewModels), которые отделяют данные, возвращаемые от ваших сущностей.
Для этого Службам потребуется ссылка на ваши сущности, поскольку именно таков уровень данных. В идеале уровень данных должен возвращать IQueryable<TEntity>
, а не IEnumerable<TEntity>
или даже TEntity
, чтобы службы могли уточнить запросы для эффективности, не возвращая больше данных, чем им нужно, или добавляя много сложности или большое количество подобных одноцелевые методы в слое данных.
Я не уверен, каково ваше отвращение к Automapper, но он идеально подходит для обработки преобразования и копирования данных между Entity и ViewModel. Безусловно, вы можете сделать это без Automapper, используя Select
метод Linq.
Например, чтобы получить список OrderModels из вашего сервиса:
public IEnumerable<OrderModel> GetOrdersForCustomer(int customerId, int pageNumber, int pageSize)
{
var orders = OrderRepository.GetByCustomerId(customerId)
.OrderByDescending(x => x.CreatedAt)
.Select(x => new OrderModel
{
OrderId = x.OrderId,
OrderNumber = x.OrderNumber,
CreatedAt = x.CreatedAt,
// ... et al,
OrderItems = x.OrderItems.Select( oi => new OrderItemModel
{
OrderItemId = oi.OrderItemId,
ProductId = oi.Product.ProductId,
ProductName = oi.Product.Name,
Quantity = oi.Quantity,
UnitPrice = oi.Product.Price,
// ...
}).ToList()
}).Skip(pageNumber*pageSize)
.Take(pageSize)
.ToList();
return orders;
}
Неуклюжий, но гибкий Работа выполнена. С настроенным сопоставлением Automapper:
public IEnumerable<OrderModel> GetOrdersForCustomer(int customerId, int pageNumber, int pageSize)
{
var orders = OrderRepository.GetByCustomerId(customerId)
.OrderByDescending(x => x.CreatedAt)
.ProjectTo<OrderModel>()
.Skip(pageNumber*pageSize)
.Take(pageSize)
.ToList();
return orders;
}
Ключ к чему-то подобному, работающему эффективно с точки зрения производительности и использования памяти, заключается в том, что OrderRepository.GetCustomerById(int)
возвращает IQueryable<Order>
не IEnumerable<Order>
Это позволяет Select
/ ProjectTo
и Skip
& Take
для компиляции до SQL, который возвращает только столбцы, необходимые для заполнения ваших моделей.
ViewModels / DTOs не должны соответствовать своим аналогам Entity 1: 1. Вам нужно только включить поля, которые, как вы знаете, будут использовать потребители. Это помогает защитить вашу схему, но также может оптимизировать объем данных, передаваемых по беспроводной сети, повышая производительность и уменьшая использование памяти сервера. Вы можете определить столько моделей представлений, сколько вам нужно для сущности, и использовать наследование, чтобы расширить их, если это необходимо.
При переходе другим способом, например при выполнении вставки или обновления, Automapper может помочь упростить переходы данных, и даже охватывают правила, чтобы гарантировать, что данные, которые не должны быть изменены, никогда не будут изменены. Вы можете загрузить графы сущностей в вашем сервисе, как описано выше, но с помощью Include
для предварительной выборки связанных данных, которые могут быть обновлены, и выбора конкретной сущности для обновления. С Automapper вы можете обрабатывать как вставные сценарии ios, так и обновления:
Вставки:
var newOrder = _mapper.Map<Order>(orderViewModel);
_context.Orders.Add(newOrder);
_context.SaveChanges();
Обновления:
var existingOrder = _context.Orders.Single(x => x.OrderId = orderViewModel.OrderId);
_mapper.Map(orderViewModel, existingOrder);
_context.SaveChanges();
... и для отдельных объектов, которые вы в значительной степени сделано. Может потребоваться немного больше работы для обновления связанных сущностей с Заказом.