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