We have ASP MVC web project. After reading a lot of articles and discussions here in stackoverflow about the correct architechture we have decided to go with the following one, although there is not only one correct way of doing things this is the way we have decided, but we still have some doubts.
We are publishing this here not only to be helped but also to show what we have done in case it is helpful to somebody.
We are working in ASP .NET MVC project, EF6 Code first with MS SQL Server.
We have divided the project into 3 main layers that we have separate into 3 projects: model, service and web.
The model creates the entities and setup the DataContext for the database.
The service make the queries to the data base and transform those entities into DTOs to pass them to the web layer, so the web layer doesn't know anything about the database.
The web uses AutoFac for the DI (dependency Injection) to call the services we have in the service layer and obtain the DTOs to transform those DTOs into Model Views to use them in the Views.
After reading a lot of articles we decided not to implement a repository pattern and unit of work because, in summary, we have read the EF acts as a unit of work itself. So we are simplifying things a little here.
https://cockneycoder.wordpress.com/2013/04/07/why-entity-framework-renders-the-repository-pattern-obsolete/
This is the summary of our project. Now I'm going to go through every project to show the code. We are going to show only a couple of entities, but our project has more than 100 different entities.
MODEL
Data Context
public interface IMyContext
{
IDbSet<Language> Links { get; set; }
IDbSet<Resources> News { get; set; }
...
DbSet<TEntity> Set<TEntity>() where TEntity : class;
DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
}
public class MyDataContext : DbContext, IMyContext
{
public MyDataContext() : base("connectionStringName")
{
}
public IDbSet<Language> Links { get; set; }
public IDbSet<Resources> News { get; set; }
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
}
}
Here is how we declare the entities
public class Link
{
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public bool Active { get; set; }
}
SERVICES
These are the generic classes we use for all the services.
As you see we use the DTOs to get data from the web layer. Also we connect to the database using Dbset = Context.Set()
public interface IService
{
}
public interface IEntityService<TDto> : IService where TDto : class
{
IEnumerable<TDto> GetAll();
void Create(TDto entity);
void Update(TDto entity);
void Delete(TDto entity);
void Add(TDto entity);
void Entry(TDto existingEntity, object updatedEntity);
void Save();
}
public abstract class EntityService<T, TDto> : IEntityService<TDto> where T : class where TDto : class
{
protected IClientContext Context;
protected IDbSet<T> Dbset;
protected EntityService(IClientContext context) { Context = context; Dbset = Context.Set<T>(); }
public virtual IEnumerable<TDto> GetAll()
{
return Mapper.Map<IEnumerable<TDto>>(Dbset.AsEnumerable());
}
public virtual void Create(TDto entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
Dbset.Add(Mapper.Map<T>(entity));
Context.SaveChanges();
}
public virtual void Update(TDto entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
Context.Entry(entity).State = EntityState.Modified;
Context.SaveChanges();
}
public virtual void Delete(TDto entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
Dbset.Remove(Mapper.Map<T>(entity));
Context.SaveChanges();
}
public virtual void Add(TDto entity)
{
Dbset.Add(Mapper.Map<T>(entity));
}
public virtual void Entry(TDto existingEntity, object updatedEntity)
{
Context.Entry(existingEntity).CurrentValues.SetValues(updatedEntity);
}
public virtual void Save()
{
Context.SaveChanges();
}
}
We declare the DTOs in this project (this is a very simple example so we don't have to put all the code here):
public class LinkDto
{
public int Id { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public bool Active { get; set; }
}
Then one of our services:
public interface ILinkService : IEntityService<LinkDto>
{
IPagedList<LinkDto> GetAllLinks(string searchTitle = "", bool searchActive = false, int pageNumber = 1, int pageSize = 10);
LinkDto FindById(int id);
LinkDto Test();
}
public class LinkService : EntityService<Link, LinkDto>, ILinkService
{
public LinkService(IClientContext context) : base(context) { Dbset = context.Set<Link>(); }
public virtual IPagedList<LinkDto> GetAllLinks(bool searchActive = false, int pageNumber = 1, int pageSize = 10)
{
var links = Dbset.Where(p => p.Active).ToPagedList(pageNumber, pageSize);
return links.ToMappedPagedList<Link, LinkDto>();
}
public virtual LinkDto FindById(int id)
{
var link = Dbset.FirstOrDefault(p => p.Id == id);
return Mapper.Map<LinkDto>(link);
}
public LinkDto Test()
{
var list = (from l in Context.Links
from o in Context.Other.Where(p => p.LinkId == l.Id)
select new OtherDto
{ l.Id, l.Title, l.Url, o.Other1... }).ToList();
return list;
}
}
As you see we use AutoMapper (version 5 which has changed a little) to transform from Entities to DTOs the data.
One of the doubts we have is if the use of "Dbset.Find" or "Dbset.FirstOrDefault" is correct and also if the use of "Context.Links" (for any entity).
WEB
FInally the web project where we receive the DTOs and transform those DTOs into ModelViews to show in our views.
We need to call, in the Global.asax Application_Start, AutoFac to do the DI so we can use our services.
protected void Application_Start()
{
...
Dependencies.RegisterDependencies();
AutoMapperBootstrapper.Configuration();
...
}
public class Dependencies
{
public static void RegisterDependencies()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired();
builder.RegisterModule(new ServiceModule());
builder.RegisterModule(new EfModule());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
public class ServiceModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("MyProject.Service")).Where(t => t.Name.EndsWith("Service")).AsImplementedInterfaces().InstancePerLifetimeScope();
}
}
public class EfModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType(typeof(MyDataContext)).As(typeof(IMyContext)).InstancePerLifetimeScope();
}
}
As you see we also call AutoMapper to configure the different maps.
Then in our controllers we have this.
public class LinksController : Controller
{
private readonly ILinkService _linkService;
public LinksController(ILinkService linkService)
{
_linkService = linkService;
}
public ActionResult Index()
{
var links = _linkService.GetAllLinks();
return View(links.ToMappedPagedList<LinkDto, LinksListModelAdmin>());
}
...
public ActionResult Create(LinksEditModelAdmin insertedModel)
{
try
{
if (!ModelState.IsValid) return View("Create", insertedModel);
var insertedEntity = Mapper.Map<LinkDto>(insertedModel);
_linkService.Create(insertedEntity);
return RedirectToAction("Index");
}
catch (Exception ex)
{
throw ex;
}
}
}
Well, this is it...I hope this can be useful for somebody...and also I hope we can have a little help with the questions we have.
1) Although we are separating database from the web project we do need a reference in the web project to initialize the database and also to inject dependencies, is this correct?
2) Is it correct the approach we have done having our Entities->DTOs->ViewModels? It's a little more work but we have everything separated.
3) In the Service project, when we need to reference a different entity than the main one we are using in the service, is it correct to call Context.Entity?
For example, if we need to retrieve also data from the News entity in the links service, is it correct to call "Context.News.Where..."?
4) We do have a little problem with Automapper and EF proxy, because when we call "Dbset" to retrieve data, it gets a "Dynamic proxies" object so Automapper can't find the proper map so, in order to work, we have to set ProxyCreationEnabled = false in the DataContext definition. This way we can get an Entity in order to map it to the DTO. This disables LazyLoading, which we don't mind, but is this a correct approach or there is a better way to solve this?
Thanks in advance for your comments.
For Question no. 2
Entities->DTOs->ViewModels? is good approach
because you are doing the clean separation, the programmer can work together with ease.
The person who design ViewModels, Views and Controllers don't have to worry about the service layer or the DTO implementation because he will make the mapping when the others developpers finish their implementation.
For Question no. 4
When the flag ProxyCreationEnabled is set to false, the proxy instance will not be created with creating a new instance of an entity. This might not be a problem but we can create a proxy instance using the Create method of DbSet.
using (var Context = new MydbEntities())
{
var student = Context.StudentMasters.Create();
}
The Create method has an overloaded version that accepts a generic type. This can be used to create an instance of a derived type.
using (var Context = new MydbEntities())
{
var student = Context.StudentMasters.Create<Student>();
}
The Create method just creates the instance of the entity type if the proxy type for the entity would have no value (it is nothing to do with a proxy). The Create method does not add or attach the entity with the context object.
Also i read some where if you set ProxyCreationEnabled = false the child element will not loaded for some parent object unless Include method is called on parent object.
Related
Entity Framework Core is not returning any results. I've been searching near and far. I find some tutorials saying one thing and others saying another. Here is what I have so far:
Buyer.cs
[Table("DealerCustomer", Schema = "dbo")]
public class Buyer
{
[Key]
public int DealerCustomerKey { get; set; }
public int DealerId { get; set; }
}
BuyerContext.cs
public class BuyerContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer("db connection string here");
}
public DbSet<Buyer> Buyers { get; set; }
}
Startup.cs > ConfigureServices function
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddDbContext<BuyerContext>(options =>
options.UseSqlServer("db connection string here");
services.AddMvc();
}
Now I am trying to load the Buyers data from my BuyerController.cs:
[Route("api/[controller]")]
public class BuyersController : Controller
{
private BuyerContext _context;
public BuyersController(BuyerContext context)
{
_context = context;
}
[HttpGet]
public IEnumerable<Buyer> Get()
{
System.Diagnostics.Debug.WriteLine("getting buyers");
System.Diagnostics.Debug.WriteLine(_context.Buyers);
return _context.Buyers;
}
}
This is all returning empty brackets when I load the page, instead of a list of Buyers. However there are over 1000 rows in that table (dbo.DealerCustomer). I know I have two places adding the db connection string but tutorials kept show both ways of doing it and when I only did it in startup.cs I was getting errors about the _context. I can make everything look pretty later, right now I just want a good connection so I have a starting place to work from.
I found there was a timeout because one of the decimal fields was returning null.
EF Core Timing out on null response
I would like to take a model of a table and make a viewmodel with additions fields, then populate it as a view. How do I do this in the controller? When it was just a model it worked but I'm not sure how to do the same thing when it's a viewmodel. I'm new to asp.net mvc so any help would be appreciated.
Model:
public partial class tblTag
{
public int TagId { get; set; }
public string TagName { get; set; }
}
ViewModel:
public class tblTagViewModel
{
public string TagName { get; set; }
public string TagNameClr
{
get
{
if (TagName == "Test")
{
return "green";
}
else
{
return "red";
}
}
}
}
Controller:
Cannot implicitly convert type 'System.Data.Entity.DbSet<_1MvcSqlServer.Models.tblTag>' to '_1MvcSqlServer.ViewModel.tblTagViewModel'
Now that it's a viewmodel the structure from entities is different, I assume that is the problem. How do I resolve this?
private testEntities db = new testEntities();
public ActionResult Test()
{
ViewModel.tblTagViewModel model = new ViewModel.tblTagViewModel();
model = db.tblTags;
return(model);
}
Here is what I ended up with to get it to work. Is this the correct method?
public ActionResult Test()
{
List<ViewModel.tblTagViewModel> list = new List<ViewModel.tblTagViewModel>();
var model = new ViewModel.tblTagViewModel();
foreach(tblTag p in db.tblTags)
{
ViewModel.tblTagViewModel nw = new ViewModel.tblTagViewModel();
nw.TagName = p.TagName;
list.Add(nw);
}
return View(list);
}
Correct method - create Business layer, which would get data from DB layer and pass this data to View layer (and vice versa, get data from View layer and pass to DB layer).
Also, I recommend to look at IoC (DI) approach :
Business Layer:
First at all, you create new ClassLibrary project and create Business layer POCO classes and an abstraction of your repository:
/// <summary>
/// this is a Business layer class, which is very similar on EF class
/// </summary>
public class Tag
{
public int TagId { get; set; }
public string TagName { get; set; }
}
public interface IRepository
{
List<Tag> GetTagList();
}
Then you create Service:
public class Service
{
private readonly IRepository _repository;
public Service(IRepository repository)
{
if (repository == null)
throw new ArgumentNullException("repository");
_repository = repository;
}
public List<Tag> GetTagList()
{
return _repository.GetTagList();
}
}
on this stage you have compiled version of Business layer. You can add all complex logic of your application and test it. Attent, without DB amd View parts!
Next step - create DB layer
DB Layer:
Create one more Class Library in your solution, add link to BL project and add code like this:
public class EFRepository : IRepository
{
private ... _db;
public EFRepository()
{
.....
}
public List<Tag> GetTagList()
{
var tags = (from i in _db.tblTags select new Tag { TagId = i.TagId, TagName = i.TagName }).ToList();
}
}
so, you have DB and BL layers. And your BL layer is not depend on DB layer, vice versa, DB layer is depend on BL layer .And it's a correct approach, business logic of your application should not be depended on DB!
The last step - View Layer.
View Layer
You can separate View Layer to different project too. I do it inside my ASP.NET MVC application. Add references to DB and BL projects
public class tblTagViewModel
{
public string TagName { get; set; }
public string TagNameClr
{
get
{
if (TagName == "Test")
{
return "green";
}
else
{
return "red";
}
}
}
}
and your controller:
public class HomeController : Controller
{
private readonly Service _service;
public HomeController(IRepository repository)
{
if (repository == null)
throw new ArgumentNullException("repository");
_service = new Service(repository);
}
public ActionResult Test()
{
List<ViewModel.tblTagViewModel> list = new List<ViewModel.tblTagViewModel>();
vat taglist = _service.GetTagList();
foreach (tblTag p in taglist)
{
ViewModel.tblTagViewModel nw = new ViewModel.tblTagViewModel();
nw.TagName = p.TagName;
list.Add(nw);
}
return View(list);
}
}
and, the last step, set DB implementation to your web app:
public class MyControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
var efRepository = new EFRepository();
if (controllerType == typeof(HomeController))
{
return new HomeController(efRepository );
}
return base.GetControllerInstance(requestContext, controllerType);
}
pay attention, that your web-application is not depend on DB layer, it depends only on BL layer (this is logical too). Only one place to set EFRepository in your web application - in controller factory.
With this approach you can easily change EFRepository to another implementation of IRepository, with different DB or even fake DB. You can test any part of your application without unnecessary relationships. You have strong and correct architecture, where your application depend on business logic layer and not depend on DB layer etc. Any changes in DB layer or View layer are not affect on logic layer
For mapping similar POCO classes (i.e BL and DB, BL and View classes) with many properties you can user Automapper
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
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();
I've been looking at a few blog posts to try and create an appropriate solution for the following requirements but I can't seem to piece them together. Hope fully someone can help.
I've been using Repository pattern with interfaces using Automapper...here's a trimmed down example:
public class BookingRepository : IBookingRepository
{
Entities context = new Entities();
public IEnumerable<BookingDto> GetBookings
{
get { return Mapper.Map<IQueryable<Booking>, IEnumerable<BookingDto>>(context.Bookings); }
}
public BookingDto GetBookingWithProduct(Guid bookingId)
{
return Mapper.Map<BookingDto>(context.Bookings.Include(c => c.Products).SingleOrDefault(c => c.BookingId == bookingId));
}
public void Update(BookingDto bookingDto)
{
var booking = Mapper.Map<Booking>(bookingDto);
context.Entry(booking).State = EntityState.Modified;
}
public void Save()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
public interface IBookingRepository : IDisposable
{
IEnumerable<BookingDto> GetBookings { get; }
BookingDto GetBooking(Guid bookingId);
void Update(BookingDto bookingDto);
void Save();
}
With a seperate Repository for a different Entity, for example
public class ProductRepository : IProductRepository
{
Entities context = new Entities();
public IEnumerable<ProductDto> GetProducts
{
get { return Mapper.Map<IQueryable<Product>, IEnumerable<ProductDto>>(context.Products); }
}
public ProductDto GetProductWithDesign(int productId)
{
return Mapper.Map<ProductDto>(context.Products.Include(c => c.Designs).SingleOrDefault(c => c.ProductId == productId));
}
public void Update(ProductDto productDto)
{
var product = Mapper.Map<Product>(productDto);
context.Entry(product).State = EntityState.Modified;
}
public void Save()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
public interface IProductRepository : IDisposable
{
IEnumerable<ProductDto> GetProducts { get; }
ProductDto GetProduct(int productId);
void Update(ProductDto productDto);
void Save();
}
Then in my Controller I'm using the repositories as so:
public class HomeController : Controller
{
private readonly IBookingRepository bookingRepository;
private readonly IProductRepository productRepository;
public HomeController() : this(new BookingRepository(), new ProductRepository()) { }
public HomeController(IBookingRepository bookingRepository, IProductRepository productRepository)
{
this.bookingRepository = bookingRepository;
this.productRepository = productRepository;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing && this.bookingRepository != null)
this.bookingRepository.Dispose();
if (disposing && this.productRepository != null)
this.productRepository.Dispose();
}
}
So now I'm hoping to create a Unit Of Work to abstract these repositories and share the context and also create a generic repository for the duplicated actions (Save and Update) bearing in mind I'm passing in Dtos and Mapping to Entity objects. I'm having difficulty understanding how to knit it all together.
Additionally, I've seen this post
Repository pattern with generics and DI
which states "You should not have other repository interfaces besides your generic repository" and that custom queries "deserve their own (generic) abstraction:" which is adding another complication to my overworked brain as my repositories will have custom queries that return complex linked objects using Include Statements as Lazy Loading is disabled.
So I'm prepared to be shot down and told that I'm going about this the wrong way but would be grateful for any direction given.
Thanks in advance.
Don't use generic repositories. They are all leaky abstractions. Ask yourself, what benefit to you get by using an abstraction that doesn't really abstract away something? You could use your OR/M directly in those cases.
What I means is that anything that exposes IQueryable<T> forces the user to learn about the weaknesses that the underlying OR/M has. Examples: How do the orm handle lazy loading? How do I eagerly load related entities? How do I create a IN clause?
If you truly want to use the repository pattern either use it together with the specification pattern (you can keep on using a generic repository then) or create repositories that are specific for each root aggregate.
I've blogged about it: http://blog.gauffin.org/2013/01/repository-pattern-done-right/
What I usually do in this case is to create a Base abstract Repository class like this:
public abstract class BaseRepository<T> : IRepository<T>
{
Entities context = new Entities();
public virtual T GetAll()
{
return context.Set<T>();
}
// Add base implementation for normal CRUD here
}
If you don't need special queries then you don't need to create special interface and classes (but you can of course, to improve readability). So you will use, for example:
var bookingsRepo = new BaseRepository<BookingsDto>();
var allBookings = bookingsRepo.GetAll();
If you need some special queries, you create an interface that extends the base interface:
public interface IProductRepository : IRepository<Product>
{
Product GetSpecialOffer();
}
Then create your class:
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
public Product GetSpecialOffer()
{
// your logic here
}
}
That way you only specify a minimal number of special cases while relying on the Base abstract implementation for all things normal.
I added virtual to the base methods because I always like to give derived class the ability to override stuff...
We are using AutoMapper extensively in our ASP.NET MVC web applications with the AutoMapViewResult approach set out in this question. So we have actions that look like this:
public ActionResult Edit(User item)
{
return AutoMapView<UserEditModel>(View(item));
}
This creates hidden failure points in the application if the requested mapping has not been configured - in that this is not a compile time fail.
I'm looking at putting something in place to test these mappings. As this needs to test the actual AutoMapper configuration I presume this should be done as part of integration testing? Should these tests be structured per controller or per entity? What about the possibility of automatically parsing all calls to AutoMapView?
Note that we are already testing that the AutoMapper configuration is valid using AssertConfigurationIsValid, it is missing mappings that I want to deal with.
If your controller action looked like this:
public AutoMapView<UserEditModel> Edit(User item)
{
return AutoMapView<UserEditModel>(View(item));
}
Then you can pretty easily, using reflection, look for all controller actions in your project. You then examine the action parameter types and the generic type parameter of your AutoMapView action result. Finally, you ask AutoMapper if it has a type map for those input/output models. AutoMapper doesn't have a "CanMap" method, but you can use the IConfigurationProvider methods of FindTypeMapFor:
((IConfigurationProvider) Mapper.Configuration).FindTypeMapFor(null, typeof(User), typeof(UserEditModel);
Just make sure that's not null.
[Test]
public void MapperConfiguration()
{
var mapper = Web.Dto.Mapper.Instance;
AutoMapper.Mapper.AssertConfigurationIsValid();
}
You can use the AssertConfigurationIsValid method. Details are on the automapper codeplex site (http://automapper.codeplex.com/wikipage?title=Configuration%20Validation)
Strictly speaking you should be writing a test to validate the mapping before you write a controller action that depends on the mapping configuration being present.
Either way, you can use the Test Helpers in the MvcContrib project to check the action method returns the expected ViewResult and Model.
Here's an example:
pageController.Page("test-page")
.AssertViewRendered()
.WithViewData<PortfolioViewData>()
.Page
.ShouldNotBeNull()
.Title.ShouldEqual("Test Page");
I do something like this.
using System.Linq;
using System.Reflection;
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
public class SampleDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Sample
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string LoginId { get; set; }
}
public class AutomapperConfig
{
public static void Configure()
{
Mapper.Initialize(cfg => cfg.AddProfile<ViewModelProfile>());
}
}
public class ViewModelProfile : Profile
{
protected override void Configure()
{
CreateMap<SampleDto, Sample>();
}
}
[TestClass]
public class AutoMapperTestsSample
{
public AutoMapperTestsSample()
{
AutomapperConfig.Configure();
}
[TestMethod]
public void TestSampleDtoFirstName()
{
#region Arrange
var source = new SampleDto();
source.FirstName = "Jim";
//source.LastName = "Bob";
var dest = new Sample();
dest.FirstName = "FirstName";
dest.LastName = "LastName";
dest.LoginId = "LoginId";
#endregion Arrange
#region Act
AutoMapper.Mapper.Map(source, dest);
#endregion Act
#region Assert
Assert.AreEqual("Jim", dest.FirstName);
Assert.AreEqual(null, dest.LastName);
Assert.AreEqual("LoginId", dest.LoginId);
#endregion Assert
}
[TestMethod]
public void TestSampleDtoLastName()
{
#region Arrange
var source = new SampleDto();
//source.FirstName = "Jim";
source.LastName = "Bob";
var dest = new Sample();
dest.FirstName = "FirstName";
dest.LastName = "LastName";
dest.LoginId = "LoginId";
#endregion Arrange
#region Act
AutoMapper.Mapper.Map(source, dest);
#endregion Act
#region Assert
Assert.AreEqual(null, dest.FirstName);
Assert.AreEqual("Bob", dest.LastName);
Assert.AreEqual("LoginId", dest.LoginId);
#endregion Assert
}
/// <summary>
/// This lets me know if something changed in the Dto object so I know to adjust my tests
/// </summary>
[TestMethod]
public void TestSampleDtoReflection()
{
#region Arrange
var xxx = typeof(SampleDto);
#endregion Arrange
#region Act
#endregion Act
#region Assert
Assert.AreEqual(2, xxx.GetRuntimeFields().Count());
Assert.AreEqual("System.String", xxx.GetRuntimeFields().Single(a => a.Name.Contains("FirstName")).FieldType.ToString());
Assert.AreEqual("System.String", xxx.GetRuntimeFields().Single(a => a.Name.Contains("LastName")).FieldType.ToString());
#endregion Assert
}
}