#c_sharp #архитектура #проектирование #инспекция_кода
В своих приложениях я использую подход разделение функционала на слои, слои я стараюсь группировать на основании функциональности Data Access Object Data Access Layer Business Logic Layer GUI Для работы с бд я использую entity-framework. Довольно часто я стал ловить себя на мысли что я не понимаю/не уверен на каком из слоев должна быть реализована та или иная функциональность. Например: необходимо получить список объектов из бд, пусть класс будет называться Request, тогда я делаю так: В DAL у меня есть generic репозиторий() c реализацией CRUD и торчащим наружу свойством protected virtual IQueryableEntities{...} В BLL я обращаюсь к свойству Entities вытягиваю из бд необходимые мне данные, материализую(ToList(),Single()), преобразую в DTO объекты которые отдаю в GUI. В целом меня такой подход устраивал всем, базовые методы(CRUD) реализованы в одном классе реализующим generic репозиторий, методы для работы с конкретными сущностями реализованы в слое BLL. Но тут наступил переломный момент: мне понадобилось использовать хранимые процедуры/функции. Для поддержания чистоты приложения доступ к хранимкам хочу(мне так кажется правильней) сделать в слое DAL я это представляю как то так : Сделать IRequestRepository в который в качестве зависимости передать generic репозиторий плюс реализовать необходимые методы/функции, например такие как обращение и получения результата хранимой функции, так же мне будет необходимо выставить наружу в generic репозитории DbContext что бы можно было обращаться к хранимкам(_context.Database.SqlQuery (...)), дальше же все остается так как и было. как альтернатива отказаться от generic репозитория и для каждого необходимого класса из DAO реализовать класс репозитория, но в этом случае в каждом классе будет дублирование CRUD методов, что мне кажется не правильным. Пример реализации: public interface IRepository where T: class { T GetById(object id); void Insert(T entity); void Insert(IEnumerable entities); void Update(T entity); void Update(IEnumerable entities); void Delete(T entity); void Delete(IEnumerable entities); IQueryable Table { get; } DbContext Context { get; } } public interface IRequestRepository { Request GetById(object id); void Insert(Request request); void Insert(IEnumerable requests); void Update(Request request); void Update(IEnumerable requests); void Delete(Request request); void Delete(IEnumerable requests); } public class RequestRepository: IRequestRepository { private readonly IRepository _requestRepository; public RequestRepository(IRepository requestRepository) { this._requestRepository = requestRepository; } #region Методы обертки над методами из IRepository Request GetById(object id) {...} void Insert(Request request) {...} void Insert(IEnumerable requests) {...} void Update(Request request) {...} void Update(IEnumerable requests) {...} void Delete(Request request) {...} void Delete(IEnumerable requests) {...} #endregion #region Методы обертки над хранимыми функциями/процедурами IEnumerable GetListOfActiveRequests() { var _requestList = _requestRepository.Context.Database .SqlQuery ("Select * from dbo.GetListOfActiveRequests()"); //... } #endregion //Прочие методы } Как все таки лучше/правильней поступить в описанном мной случае? UPD: исходя из ответа @PavelMayorov, если я правильно понял структура классов у меня вырисовывается следующим образом public interface IRequestRepository { #region CRUD методы Request GetById(object id); void Insert(Request request); void Insert(IEnumerable requests); void Update(Request request); void Update(IEnumerable requests); void Delete(Request request); void Delete(IEnumerable requests); #endregion } public interface IRequestHistoryRepository { #region CRUD методы #endregion } public class RequestRepository: IRequestRepository { private readonly DbContext _context; public RequestRepository(DbContext context) { this._context=context; } // Реализация необходимых методов // для работы с "сущностью" } public class RequestHistoryRepository: IRequestHistoryRepository { private readonly DbContext _context; public RequestHistoryRepository(DbContext context) { this._context=context; } // Реализация необходимых методов // для работы с "сущностью" } вот только в этом случае будет дублирование в каждом таком репозитории кода CRUD try { if (request == null) throw new ArgumentNullException("entity"); //Создание/Чтение/Редактирование/Удаление } catch (DbEntityValidationException dbEx) { var msg = string.Empty; foreach (var validationErrors in dbEx.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine; var fail = new Exception(msg, dbEx); //Debug.WriteLine(fail.Message, fail); throw fail; }
Ответы
Ответ 1
Репозиторий со свойством IQueryableEntities - это безусловно лишняя сущность - EF уже сама по себе предоставляет неплохую реализацию репозитория (DbSet ). Особенно лишними являются generic-репозитории. Вообще говоря, в простых программах слоем DAL можно считать саму библиотеку EF; делать поверх новый слой - странное занятие. Если ваша программа достаточно простая - то просто выкиньте из нее все репозитории и увидите как все стало проще :) Основная проблема слоев DAL в типичных программах - потеря инкапсуляции, схема хранения напрямую связана с внешним интерфейсом из-за использования типов модели хранения в интерфейсах. Если вам действительно нужен отдельный слой DAL - предлагаю вам выкинуть из его интерфейсов все упоминания моделей EF. Хранение данных должно быть полностью скрыто в слое DAL, никаких классов-сущностей или IQueryable<> наружу торчать не должно. Код уровня бизнес-логики не должен знать, простой ли запрос идет к базе - или же вызывается хранимая процедура. Что же до повторяющегося кода - есть стандартные способы борьбы с ним: методы-хелперы, базовые классы, AOP. Ответ 2
Не существует идеального готового архитектурного решения на все случаи жизни. Мне неизвестны программисты, которые с самого начала своей карьеры, реализовывали бы хорошие и удобные слоения приложений. Поскольку речь идет об объектно-ориентированном языке программирования C#, то я бы предложил автору вопроса хотя бы в первом приближении ознакомиться с соответствующими приемами и шаблонами проектирования и разработки и попытаться взять их на вооружение. Классический учебник на эту тему в русском переводе называется "Приемы объектно-ориентированного проектирования. Паттерны проектирования", авторы Э.Гамма, Р.Хелм, Р.Джонсон, Д.Влиссидес. На самом деле на эту же тему и именно о C# есть более читабельная книга. Авторы Р.Мартин и М.Мартин, а называется она "Принципы, паттерны и методики гибкой разработки на языке C#". Сразу скажу что прочтение вышеозначенной литературы не сделает никого мгновенно готовым архитектором.