ASP.NET MVC 5 EF 6 — только классы бизнес-логики по сравнению с репозиторием и единицей работы ОБНОВЛЕНО

За последние несколько месяцев я изучал MVC5 с EF6. Честно говоря, это был роман любви/ненависти не к самой структуре, а к онлайн-учебникам Microsoft. Не все, но большинство их уроков, кажется, заканчиваются как последний сезон телесериала. Зрители дома ломают голову, пытаясь понять остальную часть истории или причину, по которой они вообще начали ее смотреть.

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

Прежде чем рассматривать репозиторий, у меня в папке моделей был простой общедоступный класс с именем productservice.cs, который позволял мне передавать модель продукта из контроллера для выполнения некоторых манипуляций с данными и возвращать ее в представление для сохранения. С моей точки зрения, это работало безупречно, но я вызывал дополнительный dbcontext в этом сервисном классе, что, как мне кажется, казалось неверным.

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

Теперь вопрос в том, как мне получить списки для моих выпадающих списков в контроллере? Быстрое решение состояло в том, чтобы поместить Get() в мой productdepository для каждого списка. Это сработало, но кажется, что я пишу больше кода, чем нужно. Я вижу преимущество в написании отдельных репозиториев для каждой модели, поскольку они могут иметь разные методы хранения и извлечения в будущем. Будет ли правильным решением с использованием репозитория создать единицу работы, которая ссылается на каждый репозиторий, необходимый для этого контроллера? Вот часть моего кода

Интерфейс репозитория продукта:

public interface IProductRepository : IDisposable
{
    IEnumerable<Category> GetCategories();      
    IEnumerable<Manufacturer> GetManufacturers();      
    IEnumerable<ProductType> GetProductTypes();       
    IEnumerable<Availability> GetAvailabilities();
    IEnumerable<ShipMethod> GetShipMethods();       
    IEnumerable<Product> GetProducts();
    IEnumerable<Product> GetProductsByName(string productName);
    Product GetProductById(int productId);
    void InsertProduct(Product product);
    void DeleteProduct(int productId);
    void UpdateProduct(Product product);
    void Save();
}

Верхняя часть моего контроллера:

public class ProductController : Controller
{
private IProductRepository productRepository;

    public ProductController()
    {
        this.productRepository = new ProductRepository(new ProductContext())
    }

    public ProductController(IProductRepository productRepository)
    {
        this.productRepository = productRepository;
    }

    public ActionResult Create()
    {
        Product product = new Product();
        product.Created = DateTime.Now;
        ViewBag.AvailabilityId = new SelectList(productRepository.GetAvailabilities(), "AvailabilityId", "Name");
        ViewBag.CategoryId = new SelectList(productRepository.GetCategories(), "CategoryId", "Name");
        ViewBag.ManufacturerId = new SelectList(productRepository.GetManufacturers(), "ManufacturerId", "Name");
        ViewBag.ProductTypeId = new SelectList(productRepository.GetProductTypes(), "ProductTypeId", "Name");
        ViewBag.ShipMethodId = new SelectList(productRepository.GetShipMethods(), "ShipMethodId", "Name");
        return View(product);
    }

Потратив некоторое время на поиск окончательного решения, я продолжаю возвращаться к одному и тому же вопросу. Почему я не могу просто превратить IProductRepository и Repository в IProductService и ProductService?

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

Спасибо заранее за любую помощь. Просто немного запутался.

ОБНОВЛЕНИЕ. Я хотел показать пример моего оригинального контроллера и идеи службы. Я понимаю, что логика проста и может быть в контроллере, но некоторые другие мои сервисы, такие как обрезка и сохранение миниатюры и исходного изображения при создании нового продукта, занимают много места в контроллере.

Блок a от контроллера:

public class ProductTesterController : Controller
{
    private ProductContext db = new ProductContext();
    private ProductService service = new ProductService();

    // GET: Dashboard/ProductManager/Details/5
    public ActionResult Detail(int id)
    {
        Product product = db.Products.Find(id);
        if (product == null)
        {
            return HttpNotFound();
        }
        return View(product);
    } 
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(Product product)
    {
        if (ModelState.IsValid)
        {
            service.UpdatePid(product, db);
            db.Entry(product).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Detail", new { id = product.ProductId });
        }
        ViewBag.AvailabilityId = new SelectList(db.Availability, "AvailabilityId", "Name", product.AvailabilityId);
        ViewBag.CategoryId = new SelectList(db.Categories, "CategoryId", "Name", product.CategoryId);
        ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "Name", product.ManufacturerId);
        ViewBag.ProductTypeId = new SelectList(db.ProductTypes, "ProductTypeId", "Name", product.ProductTypeId);
        ViewBag.ShipMethodId = new SelectList(db.ShipMethods, "ShipMethodId", "Name", product.ShipMethodId);
        return View(product);
    }

А дальше сервис:

public class ProductService
{
    public Product CreatePid(Product product, ProductContext context)
    {
        int lastProductId = context.Products.OrderByDescending(x => x.ProductId).First().ProductId;
        product.PID = Convert.ToInt32("" + (100 * product.CategoryId) + (lastProductId + 1));
        return (product);
    }

    public Product UpdatePid(Product product, ProductContext context)
    {
        product.PID = Convert.ToInt32("" + (100 * product.CategoryId) + product.ProductId);
        return (product);
    }

}

Я не создаю новый контекст внутри службы, просто передаю контекст из контроллера и возвращаю то, что мне нужно с контекстом. Для размера моего проекта и отсутствия реального опыта работы с репозиторием/uow это, похоже, подходит для моей ситуации. Я имею в виду, что вся причина для класса обслуживания заключается в том, чтобы уменьшить размер моего контроллера и сделать его простым. Есть ли какой-либо негативный эффект от того, что вы просто делаете это, а не создаете репозиторий/uow/сервис? Спасибо!

ОБНОВЛЕНИЕ

Потратив много времени на изучение этого, я наткнулся на этот блог, который, кажется, именно то, что я искал. После этого поста я также начал использовать Autofac для внедрения моего контекста в конструкторы. Работает как шарм.

Вот ссылка, которая, надеюсь, поможет тем, кто ищет похожее решение: Как сделать Entity Framework более пригодным для модульного тестирования — Джош Кодрофф


person SeanG80    schedule 30.08.2015    source источник
comment
Можете ли вы представить, что ваше приложение масштабируется до такой степени, что вам может понадобиться отказаться от EF в пользу необработанного SQL или NOSQL и т. д.? В противном случае репозитории imho не служат никакой цели, кроме возможности издеваться над DAL. абстрагирование вашего DAL от кода - это просто добавление ненужного кода, пользователям все равно, и они, конечно, не попросят вас изменить шаблон DAL. Единственная причина, которую я вижу, - это издевательство, которое намного проще в наши дни. ИСТОЧНИК: я поддерживаю ряд приложений с репозиториями, общими репозиториями и простым сервисом с DbContext, которые легче поддерживать.   -  person SimonGates    schedule 31.08.2015
comment
Не сейчас, но никогда не знаешь. Меня больше всего беспокоит то, что мои контроллеры ограничиваются простой логикой представления и имеют место для вызова бизнес-логики как службы, когда это необходимо. Мне очень нравятся некоторые примеры CQRS, которые я видел, которые, кажется, следуют единственному подходу сборки и поддержки того, что вам нужно. К сожалению, все примеры, которые я видел, используют DI-контейнеры, такие как autofac или ninject, которые кажутся мне немного неудобными. Как вы реализуете dbcontext с сервисом? Есть где-нибудь пример? Спасибо   -  person SeanG80    schedule 01.09.2015


Ответы (1)


Я использую класс GenericRepository с классом UnitOfWork, который создает экземпляр GenericRepository для каждой модели, связывающейся через EntityFramework с базой данных. См. здесь: http://www.codeproject.com/Articles/825646/Generic-Repository-and-UnitofWork-patterns-in-MVC

Вот очень простой пример того, как я использовал это в прошлом. Стоит отметить, что для более сложных нужд SQL я использую ORM, например Dapper, и это здорово! :

dbEntities.cs

public class dbEntities : DbContext
{
    public DbSet<Product> Products{ get; set; }


    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Entity<Product>().HasKey(x => x.id);
    }
}

GenericRepository.cs

public class GenericRepository<TEntity> where TEntity :class
{
    internal dbEntities _db;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(dbEntities _db)
    {
        this._db = _db;
        this.dbSet = _db.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query);
        }
        else
        {
            return query;
        }
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Delete(TEntity entityToDelete)
    {
        if (_db.Entry(entityToDelete).State == EntityState.Detached)
        {
            dbSet.Attach(entityToDelete);
        }
        dbSet.Remove(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        _db.Entry(entityToUpdate).State = EntityState.Modified;
    }

}

UnitOfWork.cs

public class UnitOfWork :IDisposable
{
    private dbEntities _db = new dbEntities();

    private GenericRepository<Product> productRepository;


    public GenericRepository<Product> ProductRepository
    {
        get
        {
            if (this.productRepository == null)
            {
                this.productRepository = new GenericRepository<Product>(_db);
            }
            return productRepository;
        }
    }

    public void Save()
    {
        _db.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                _db.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

}

Новая запись добавляется для каждой модели в файлы unitofwork.cs и dbEntities.cs. Класс модели структурно соответствует таблице БД.

Простой пример использования

var _uow = new UnitOfWork();

//get all products over £100
var lst = _uow.ProductRepository.Get(n=>!n.price>100);
_uow.Dispose();
person scgough    schedule 30.08.2015
comment
@scgough, спасибо за ссылку. Это образец, которому я начал следовать, что заставило меня задаться вопросом, чего я действительно собираюсь достичь, внедрив его. Выше я добавил пример своей оригинальной службы, но убрал дополнительное создание контекста в классе службы и вместо этого передал контекст из контроллера. Что вы думаете? Спасибо. - person SeanG80; 31.08.2015
comment
@SeanG Я добавил гораздо больше деталей. Я надеюсь, что это поможет вам! Этот способ позволяет мне просто добавлять несколько строк каждый раз, когда я добавляю новую модель в свой проект. Я нашел базовый CRUD для малых и средних проектов. - person scgough; 01.09.2015
comment
@scgough Спасибо, что нашли время собрать это вместе! Я ценю это. Из вашего примера видно, что я бы просто использовал свои базы данных, которые у меня уже есть, и добавил их в единицу работы. Это правильно? Кроме того, с вашим универсальным репозиторием вся неразбериха обрабатывается одинаково. Используете ли вы какой-то сервисный уровень для обработки различных запросов и сохранения методов на основе модели. Я думаю, что ваш пример с использованием набора баз данных EF является наиболее логичным из тех, что я видел. Я все еще немного борюсь с тем, где вписывается сервисный уровень. Как это обрабатывается с помощью uow. Спасибо! - person SeanG80; 03.09.2015
comment
@SeanG правильно, просто добавьте наборы баз данных в единицу работы. Я не использую сервисный слой. - person scgough; 03.09.2015
comment
дайте мне знать, как вы справляетесь с этим @SeanG, если вы его используете. Отметьте как правильный ответ (если вы так считаете) и для меня тоже ;) - person scgough; 03.09.2015