Как правильно передавать объекты в N-layer архитектуре?

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

В приложении используется трехуровневая архитектура:

DAL (Data Access Layer) содержит:
  • классы Entity (сущности БД)
  • класс контекста БД (Entity Framework 6)
  • класс GenericRepository
    public interface IGenericRepository<TEntity> : IDisposable where TEntity : class
     {
            IQueryable<TEntity> GetAll();
            TEntity Find(object id);
            void Insert(TEntity item);
            void Update(TEntity item);
            void Delete(object id);
            void Dispose(bool disposing);
    }
    
    public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
            //...
    }


  • класс UnitOfWork
    public interface IUnitOfWork : IDisposable 
    {
            GenericRepository<T> Repository<T>() where T : class;
            void Save();
            void Dispose(bool disposing);
    }
    
    public class UnitOfWork<TDbContext> : IUnitOfWork where TDbContext : DbContext, new()
    {
            //...
    }



Для работы с базой данных используется Entity Framework 6.

BLL (Business Logic Layer) содержит:
  • классы DTO
  • классы сервисов
UI (Presentation layer) содержит:
  • Проект на ASP.NET MVC в котором определены ViewModels


Каждый из слоев это отдельный проект, DAL и BLL - Class Library проекты, а UI - ASP.NET MVC 4.

Вопрос заключается в следующем:
Сейчас сделано так, что GenericRepository методом GetAll возвращает IQueryable выборку всей таблицы указанной сущности TEntity, а уже в сервисе к этой выборке применяются более сложные условия, например:
public class BookService : IBookService
{
	private UnitOfWork<MyContext> Db { get; set; }
	
	public BookService()
	{
		Db = new UnitOfWork<MyContext>();
	}

	public List<BookDTO> Exmaple()
	{
		Mapper.CreateMap<Book, BookDTO>();
		return Mapper.Map<IEnumerable<Book>, List<BookDTO>>(b.Repository<Books>().GetAll().Where(b => b.BookAccepted == true && b.ParentBookId == null).ToList());
	}
}


Т.е. получается, что реальная выборка и намного более сложные запросы (например с тремя join таблицами) делаются в сервисе, но из-за этого приходится подключать Entity FrameWork и в проекте с BLL уровнем.

Насколько это правильно?
Может лучше сделать отдельный репозиторий и в нем сделать метод (в данном примере List GetAcceptedBooks()). Но в таком случае возникает следующая проблема:

Например нужно построить отчет, который можно получить из базы данных путем присоединения нескольких таблиц, что-то вроде:
SELECT b.BookName, b.BookDate, SUM(bp.BillPrice) as BookSum, p.PaidSum, np.NotPaidSum
FROM ... as b
LEFT JOIN ... as bp on ...
LEFT JOIN 
(
	SELECT BookId, SUM(BookId) as PaidSum
	FROM ...
	GROUP BY BookId
) as p on p.BookId=b.BookId
....

т.е запрос возвращает не сущность БД, а новый объект. Тогда на уровне BLL создается объект DTO, например BookReportDTO, который соответствует результату такой выборки. Но если такая выборка находится в репозитории, получается метод репозитория должен возвращать List и уровень DAL будет связан с уровнем BLL.
Будет ли это правильным?

Получается общий вопрос можно определить так: Как организовать передачу сложных объектов (НЕ СУЩНОСТЕЙ) с уровня DAL на уровень BLL? Или на уровне DAL в репозитории оставить только CRUD запросы, а уже в сервисах уровня BLL делать сложные выборки и там создавать коллекции DTO объектов?

Заранее благодарю за любую помощь!
  • Вопрос задан
  • 1967 просмотров
Решения вопроса 1
Nipheris
@Nipheris Куратор тега C#
GenericRepository и его метод GetAll - это ни о чем. В том смысле, что вам такой репозиторий не нужен - он ничего не делает и ничего не абстрагирует. Вы выставляете IQueryable - и получаете проблемы на свою голову. Да, это очень соблазнительная штука, но таки репозиторий для того, чтобы все возможные запросы оставлять там, иначе они уходят далеко в логику приложения. Вам нужен конкретный репозиторий (интерфейс GenericRepository может и пригодится) с конкретными запросами, возвращающими IEnumerable. В этом и его суть - на уровне репозитория делается ПЕРВИЧНАЯ выборка (с помощью IQueryable), которая по сути есть запрос к БД и материализация всех НУЖНЫХ объектов, а потом уже мелкую фильтрацию можно делать с объектами в IEnumerable коллекции.

Как организовать передачу сложных объектов (НЕ СУЩНОСТЕЙ) с уровня DAL на уровень BLL?

Я бы сделал свой репозиторий для каждой такой вещи. Он будет ОТЛИЧАТЬСЯ от репозитория для сущностей, т.к. там не будет, например, Update. Это будет репозиторий ЗАПИСЕЙ, не объектов. В остальном все также - все возможные запросы нужно постараться собрать там. И класс тоже можно и нужно завести вроде BookReport, только он будет immutable, т.е. будет вести себя как запись. А дальше уже делайте с ним что пожелаете.

Но если такая выборка находится в репозитории, получается метод репозитория должен возвращать List и уровень DAL будет связан с уровнем BLL.

Вот тут не понял. Ну пусть возвращает List или IEnumerable, в чем тут вы связь видите?

Вообще, возможно вам нужно поменьше париться о разделении DAL и BLL. Как правило это приводит к anemic data model, и ничего кроме вреда это вам не принисет. Вообще не знаю откуда эта мода пошла, у меня всегда получалось 80% логики оставлять в самих классах сущностей, и все было ок.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы