3
votes

Over the past few months I've been learning MVC5 with EF6. It's honestly been a love/hate affair, not with the framework itself, but with Microsoft's online tutorials. Not all, but a majority of their tutorials seem to end like the final season of a Television Series. Viewers are at home scratching their heads trying to figure out the rest of the story, or the reason why they even started watching it in the first place.

Anyway, my latest stumbling block is whether or not I should implement a repository pattern and unit of work with EF6? The reason I am interested in any sort of additional layer is just to help reduce the amount of logic code in my controllers.

Before considering a repository, I just had a simple public class in my models folder called productservice.cs that allowed me to pass a product model from the controller to perform some data manipulation and return it to the view to be saved. It worked flawlessly from my point of view, but I was calling an additional dbcontext in that service class which seemed incorrect, I think.

After some research online, I started to implement a repository which would allow me to perform the same data manipulation as in my productservice.cs, but it seemed to follow a well established pattern and allow the context to be passed from the controller to the depository where I can perform some manipulation before the save. It's more code to write considering the EF Save, Update, and Remove was working just fine, but I don't mind writing more code if it will put me in a better place moving forward.

Now the question is how do I get the lists for my dropdowns in the controller? A quick solution was to place a Get() in my productdepository for each list. It worked, but it seems like I'm writing more code than I need to. I see an advantage to writing individual repositories for each model, since they may have different store and retrieve methods down the road. Would the correct solution using repository be to create a unit of work which references each repository needed for that controller? Here's some of my code

The product repository interface:

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();
}

The top portion of my contoller:

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);
    }

After spending some time to figure out the final solution, I keep coming back to the same question. Why can't I just turn the IProductRepository and Repository into a IProductService and ProductService?

Basically keep all the CRUD in the controller and call a service if it's needed which can be passed back to the controller for final storage or presentation? What's the real point of creating a bunch of CRUD methods for each entity if EF is already doing it for me in the controller?

Thank you in advance for any help. Just a little confused.

UPDATE - I wanted to show an example of my original controller and service idea. I understand the logic is simple and could be in the controller, but some of my other services like cropping and saving both a thumbnail and original image while creating a new product take up a lot of space in the controller.

The a block from the controller:

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);
    }

And then the service:

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);
    }

}

I don't create a new context inside the service, just pass the context from the controller and return what I need with the context. For the size of my project and lack of real repository/uow experience this seems to suite my situation. I mean the whole reason for the service class is to reduce the size of my controller and keep it simple. Is there any negative effect of just doing it like this rather than creating a repository/uow/service? Thanks!

UPDATE

After spending alot of time researching this I stumbled upon this blog which seems to be exactly what I was looking for. Since this post I have also started using Autofac to inject my context into constructors. Works like a charm.

Here's the link that will hopefully help someone seeking a similar solution - Making Entity Framework More Unit Testable - Josh Kodroff

1
Can you imagine your application scaling to a point where you may need to ditch EF in favour of raw SQL or NOSQL etc? Otherwise imho repositories serve no purpose other than being able to mock the DAL. abstracting your DAL from code is just adds unneeded code, users won't care, and they certainly won't ask you to change your DAL pattern The only reason I can see is for mocking which is much easier these days. SOURCE: I maintain a number of apps with repos, generic repos and Just straight service with DbContext which imo are easier to maintain. - SimonGates
Not at this point, but you never know. My main area of concern is too keep my controllers limited to just simple presentation logic and have a place to call business logic as a service when needed. I really like some of the CQRS examples I've seen which seem to follow a "only build and maintain what you need" approach. Unfortunately, all of the examples I have seen use DI containers like autofac or ninject, which seem a little out of my comfort level. How do you implement dbcontext with service? Is there an eample somewhere? Thanks - SeanG80

1 Answers

0
votes

I use a GenericRepository class with a UnitOfWork which creates an instance of the GenericRepository for each model linking through the EntityFramework back to the database. See here: http://www.codeproject.com/Articles/825646/Generic-Repository-and-UnitofWork-patterns-in-MVC

Here is a very simple example of how I have used this in the past. It's worth noting that for more complex SQL needs I use an ORM like Dapper which is awesome! :

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);
    }

}

A new entry is added for each model in the unitofwork.cs and dbEntities.cs files. The model class corresponds structurally to a DB table.

Simple Usage example

var _uow = new UnitOfWork();

//get all products over £100
var lst = _uow.ProductRepository.Get(n=>!n.price>100);
_uow.Dispose();