Not able to add second entity to ViewModel with Automapper? - c#

I'm still learning MVC and I'm was just recently suggested to look at using AutoMapper to map my ViewModel. So, I started a test but I'm not understanding how to map two entities nor can I find an example.
What I have is a Person table and an Address table. I want to combine the two into a ViewModel to pass to my View. Looking at examples they always show how to map one entity but not two.
In the following code I can see that both my currentPerson and currentAddress objects are populated with data using the debugger but then my currentMember ViewModel only has the Person data. That makes sense because I'm only using Mapper.Map on the Person but how do I tell the currentMember to map the Address as well?
public ActionResult Edit(int id = 0)
{
using (DataContext db = new DataContext())
{
Person currentPerson = db.Person.Find(id);
Address currentAddress = db.Address.Single(a => a.PID == id);
AutoMapper.Mapper.CreateMap<Person, AdminViewModel>();
AutoMapper.Mapper.CreateMap<Address, AdminViewModel>();
MemberViewModel currentMember = AutoMapper.Mapper.Map<AdminViewModel>(currentPerson);
return View(currentMember);
}
}

AutoMapper.Map has an overload that takes a source and destination object. So you can take advantage of it using something like this:
Create ViewModels
public class PersonViewModel
{
//your person fields here..
//create them with the same name as Person
//to avoid having to set each field during mapping
}
public class AddressViewModel
{
//your address fields here..
}
public class AdminViewModel
{
public PersonViewModel Person {get; set;}
public AddressViewModel Address {get; set;}
}
Crete the mappings (You should centralize all your mappings in a class):
public class EntityToViewModelProfile : Profile
{
protected override void Configure()
{
//Create mappings for Person and Address
AutoMapper.Mapper.CreateMap<Person, AdminViewModel>();
AutoMapper.Mapper.CreateMap<Address, AdminViewModel>();
//Create a map to AdminViewModel for both Person and Address
Mapper.CreateMap<Models.Person, Models.AdminViewModel>()
.ForMember(dest => dest.Person, opt => opt.MapFrom(src => src));
Mapper.CreateMap<Models.Address, Models.AdminViewModel>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(src => src));
}
}
Register EntityToViewModelProfile in Global.asax.cs
AutoMapper.Mapper.Initialize(x =>
{
x.AddProfile<EntityToViewModelProfile>();
});
Then, in your controller you use the overload of .Map
using (DataContext db = new DataContext())
{
Person currentPerson = db.Person.Find(id);
Address currentAddress = db.Address.Single(a => a.PID == id);
var mapPerson = AutoMapper.Mapper.Map<Person, AdminViewModel>(currentPerson);
var mapPersonAndAddress = AutoMapper.Mapper.Map<Address, AdminViewModel>(currentAddress, mapPerson);
return View(mapPersonAndAddress);
}

Answer from Jpgrassi looks good and it's pretty cool way to convert. But If you are doing the conversions in a DB or BL layer (rather than the Controller itself), keeping your conversions to Business Objects to BOViewModel (Person to PersonViewModel etc.) in a is more reusable. More often you need a separate project which defines your BOViewModels (Something like MyProject.BO)
I would rather follow the simple and straightforward approach to create the AdminViewModel,
var personViewModel = AutoMapper.Mapper.CreateMap<Person, PersonViewModel>();
var addressViewModel = AutoMapper.Mapper.CreateMap<Address, AddressViewModel>();
These conversions are useful to convert DB objects to BO, which would ideally takes place in a DB or BL layer. Then pass the personViewModel and addressViewModel to Controller and create the AdminViewModel in the controller.
This is simple and this pattern works across the service boundaries or applications with many layers.

Related

Automapper initialize for T type

I want to have generic method to get data from database and pass model of how output data should look like.
I wrote simple method:
public IEnumerable<T> GetUsers<T>()
{
Mapper.Initialize(cfg =>
cfg.CreateMap<IQueryable<User>, IQueryable<T>>());
return OnConnect<IEnumerable<T>>(db =>
{
return db.Users.ProjectTo<T>().ToList();
});
}
Now I expected that I can do this:
var users = repo.GetUsers<UserViewModel>(); // it should be IEnumerable<UserViewModel>
var anotherUsers = repo.GetUsers<AnotherUserViewModel>(); // it should be IEnumerable<AnotherUserViewModel>
But I cant reinitialize automapper again. What should I do to make it working?
Initialize automapper only once per application startup.
You should know what types can be mapped from User already at the moment when you design a code in that case you can register all of them at a startup like this:
Mapper.Initialize(cfg => {
cfg.CreateMap<User, UserDto1>();
cfg.CreateMap<User, UserDto2>();
...
cfg.CreateMap<User, UserDtoN>();
});
Even if you will achieve it - it will not make a sense to try to map User to Order, but your architectural design will give that possibility
If you still want to do it(like I wrote in comments) - you can add somekind of marker attribute for Instance - MappableFrom(Type from), mark all DTO objects that can be used in scope of automapper. Then on initialization of your application - scan the assembly for all types that contains that attribute and register in Automapper.
You can use Profile to create all mappers follow this link http://docs.automapper.org/en/stable/Configuration.html
Another approach you can initialize in a static constructor all the mapping you want by using some naming convention
In the below code, I'm mapping from same object type to same object type
// Data or View Models
public class AddressViewModel : BaseViewModel
{
public string Address {get;set;}
public AddressViewModel()
{
this.Address ="Address";
}
}
public class UserViewModel : BaseViewModel
{
public string Name {get;set;}
public UserViewModel()
{
this.Name ="Name";
}
}
public class BaseViewModel
{
}
Repository -- here I'm using same view model you should create Models here
public class CrudRepo
{
public IEnumerable<T> GetData<T>() where T : class, new ()
{
var data = new List<T> { new T() };
return AutoMapper.Mapper.Map<IEnumerable<T>>(data);
}
}
Then in of the static constructor initialize the mappers
static HelperClass()
{
// In this case all classes are present in the current assembly
var items = Assembly.GetExecutingAssembly()
.GetTypes().Where(x =>
typeof(BaseViewModel)
.IsAssignableFrom(x))
.ToList();
AutoMapper.Mapper.Initialize(cfg =>
{
items.ForEach(x =>
{
// Here use some naming convention or attribute to find out the Source and Destination Type
//Or use a dictionary which gives you source and destination type
cfg.CreateMap(x, x);
});
});
}
Now you can create the instance of crud repository and get mapped items
var userRepo = new CrudRepo();
var users = userRepo.GetData<UserViewModel>();
var address = addressRepo.GetData<AddressViewModel>();
Note: As long as property names and types are same the data will be mapped else you have to create ForMember

ASP.NET Core with EF Core - DTO Collection mapping

I am trying to use (POST/PUT) a DTO object with a collection of child objects from JavaScript to an ASP.NET Core (Web API) with an EF Core context as my data source.
The main DTO class is something like this (simplified of course):
public class CustomerDto {
public int Id { get;set }
...
public IList<PersonDto> SomePersons { get; set; }
...
}
What I don't really know is how to map this to the Customer entity class in a way that does not include a lot of code just for finding out which Persons had been added/updated/removed etc.
I have played around a bit with AutoMapper but it does not really seem to play nice with EF Core in this scenario (complex object structure) and collections.
After googling for some advice around this I haven't found any good resources around what a good approach would be. My questions is basically: should I redesign the JS-client to not use "complex" DTOs or is this something that "should" be handled by a mapping layer between my DTOs and Entity model or are there any other good solution that I am not aware of?
I have been able to solve it with both AutoMapper and and by manually mapping between the objects but none of the solutions feels right and quickly become pretty complex with much boilerplate code.
EDIT:
The following article describes what I am referring to regarding AutoMapper and EF Core. Its not complicated code but I just want to know if it's the "best" way to manage this.
(Code from the article is edited to fit the code example above)
http://cpratt.co/using-automapper-mapping-instances/
var updatedPersons = new List<Person>();
foreach (var personDto in customerDto.SomePersons)
{
var existingPerson = customer.SomePersons.SingleOrDefault(m => m.Id == pet.Id);
// No existing person with this id, so add a new one
if (existingPerson == null)
{
updatedPersons.Add(AutoMapper.Mapper.Map<Person>(personDto));
}
// Existing person found, so map to existing instance
else
{
AutoMapper.Mapper.Map(personDto, existingPerson);
updatedPersons.Add(existingPerson);
}
}
// Set SomePersons to updated list (any removed items drop out naturally)
customer.SomePersons = updatedPersons;
Code above written as a generic extension method.
public static void MapCollection<TSourceType, TTargetType>(this IMapper mapper, Func<ICollection<TSourceType>> getSourceCollection, Func<TSourceType, TTargetType> getFromTargetCollection, Action<List<TTargetType>> setTargetCollection)
{
var updatedTargetObjects = new List<TTargetType>();
foreach (var sourceObject in getSourceCollection())
{
TTargetType existingTargetObject = getFromTargetCollection(sourceObject);
updatedTargetObjects.Add(existingTargetObject == null
? mapper.Map<TTargetType>(sourceObject)
: mapper.Map(sourceObject, existingTargetObject));
}
setTargetCollection(updatedTargetObjects);
}
.....
_mapper.MapCollection(
() => customerDto.SomePersons,
dto => customer.SomePersons.SingleOrDefault(e => e.Id == dto.Id),
targetCollection => customer.SomePersons = targetCollection as IList<Person>);
Edit:
One thing I really want is to delcare the AutoMapper configuration in one place (Profile) not have to use the MapCollection() extension every time I use the mapper (or any other solution that requires complicating the mapping code).
So I created an extension method like this
public static class AutoMapperExtensions
{
public static ICollection<TTargetType> ResolveCollection<TSourceType, TTargetType>(this IMapper mapper,
ICollection<TSourceType> sourceCollection,
ICollection<TTargetType> targetCollection,
Func<ICollection<TTargetType>, TSourceType, TTargetType> getMappingTargetFromTargetCollectionOrNull)
{
var existing = targetCollection.ToList();
targetCollection.Clear();
return ResolveCollection(mapper, sourceCollection, s => getMappingTargetFromTargetCollectionOrNull(existing, s), t => t);
}
private static ICollection<TTargetType> ResolveCollection<TSourceType, TTargetType>(
IMapper mapper,
ICollection<TSourceType> sourceCollection,
Func<TSourceType, TTargetType> getMappingTargetFromTargetCollectionOrNull,
Func<IList<TTargetType>, ICollection<TTargetType>> updateTargetCollection)
{
var updatedTargetObjects = new List<TTargetType>();
foreach (var sourceObject in sourceCollection ?? Enumerable.Empty<TSourceType>())
{
TTargetType existingTargetObject = getMappingTargetFromTargetCollectionOrNull(sourceObject);
updatedTargetObjects.Add(existingTargetObject == null
? mapper.Map<TTargetType>(sourceObject)
: mapper.Map(sourceObject, existingTargetObject));
}
return updateTargetCollection(updatedTargetObjects);
}
}
Then when I create the mappings I us it like this:
CreateMap<CustomerDto, Customer>()
.ForMember(m => m.SomePersons, o =>
{
o.ResolveUsing((source, target, member, ctx) =>
{
return ctx.Mapper.ResolveCollection(
source.SomePersons,
target.SomePersons,
(targetCollection, sourceObject) => targetCollection.SingleOrDefault(t => t.Id == sourceObject.Id));
});
});
Which allow me to use it like this when mapping:
_mapper.Map(customerDto, customer);
And the resolver takes care of the mapping.
AutoMapper is the best solution.
You can do it very easily like this :
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CustomerDto, Customer>();
Mapper.CreateMap<Person, PersonDto>();
Mapper.CreateMap<PersonDto, Person>();
Note : Because AutoMapper will automatically map the List<Person> to List<PersonDto>.since they have same name, and there is already a mapping from Person to PersonDto.
If you need to know how to inject it to ASP.net core,you have to see this article : Integrating AutoMapper with ASP.NET Core DI
Auto mapping between DTOs and entities
Mapping using attributes and extension methods
First I would recommend using JsonPatchDocument for your update:
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody] JsonPatchDocument<CustomerDTO> patchDocument)
{
var customer = context.EntityWithRelationships.SingleOrDefault(e => e.Id == id);
var dto = mapper.Map<CustomerDTO>(customer);
patchDocument.ApplyTo(dto);
var updated = mapper.Map(dto, customer);
context.Entry(entity).CurrentValues.SetValues(updated);
context.SaveChanges();
return NoContent();
}
And secound you should take advantage of AutoMapper.Collections.EFCore. This is how I configured AutoMapper in Startup.cs with an extension method, so that I´m able to call services.AddAutoMapper() without the whole configuration-code:
public static IServiceCollection AddAutoMapper(this IServiceCollection services)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.UseEntityFrameworkCoreModel<MyContext>(services);
cfg.AddProfile(new YourProfile()); // <- you can do this however you like
});
IMapper mapper = config.CreateMapper();
return services.AddSingleton(mapper);
}
This is what YourProfile should look like:
public YourProfile()
{
CreateMap<Person, PersonDTO>(MemberList.Destination)
.EqualityComparison((p, dto) => p.Id == dto.Id)
.ReverseMap();
CreateMap<Customer, CustomerDTO>(MemberList.Destination)
.ReverseMap();
}
I have a similar object-graph an this works fine for me.
EDIT
I use LazyLoading, if you don´t you have to explicitly load navigationProperties/Collections.
I was struggling with the very same issue for quite some time. After digging through many articles I've came up with my own implementation which I'm sharing with you.
First of all I've created a custom IMemberValueResolver.
using System;
using System.Collections.Generic;
using System.Linq;
namespace AutoMapper
{
public class CollectionValueResolver<TDto, TItemDto, TModel, TItemModel> : IMemberValueResolver<TDto, TModel, IEnumerable<TItemDto>, IEnumerable<TItemModel>>
where TDto : class
where TModel : class
{
private readonly Func<TItemDto, TItemModel, bool> _keyMatch;
private readonly Func<TItemDto, bool> _saveOnlyIf;
public CollectionValueResolver(Func<TItemDto, TItemModel, bool> keyMatch, Func<TItemDto, bool> saveOnlyIf = null)
{
_keyMatch = keyMatch;
_saveOnlyIf = saveOnlyIf;
}
public IEnumerable<TItemModel> Resolve(TDto sourceDto, TModel destinationModel, IEnumerable<TItemDto> sourceDtos, IEnumerable<TItemModel> destinationModels, ResolutionContext context)
{
var mapper = context.Mapper;
var models = new List<TItemModel>();
foreach (var dto in sourceDtos)
{
if (_saveOnlyIf == null || _saveOnlyIf(dto))
{
var existingModel = destinationModels.SingleOrDefault(model => _keyMatch(dto, model));
if (EqualityComparer<TItemModel>.Default.Equals(existingModel, default(TItemModel)))
{
models.Add(mapper.Map<TItemModel>(dto));
}
else
{
mapper.Map(dto, existingModel);
models.Add(existingModel);
}
}
}
return models;
}
}
}
Then I configure AutoMapper and add my specific mapping:
cfg.CreateMap<TDto, TModel>()
.ForMember(dst => dst.DestinationCollection, opts =>
opts.ResolveUsing(new CollectionValueResolver<TDto, TItemDto, TModel, TItemModel>((src, dst) => src.Id == dst.SomeOtherId, src => !string.IsNullOrEmpty(src.ThisValueShouldntBeEmpty)), src => src.SourceCollection));
This implementation allows me to fully customize my object matching logic due to keyMatch function that is passed in constructor. You can also pass an additional saveOnlyIf function if you for some reason need to verify passed objects if they are suitable for mapping (in my case there were some objects that shouldn't be mapped and added to collection if they didn't pass an extra validation).
Then e.g. in your controller if you want to update your disconnected graph you should do the following:
var model = await Service.GetAsync(dto.Id); // obtain existing object from db
Mapper.Map(dto, model);
await Service.UpdateAsync(model);
This works for me. It's up to you if this implementation suits you better than what author of this question proposed in his edited post:)

Mapping domain model to view model via AutoMapper or not

I want to use view model for display insted of domain model. And I want to customise a property for display, how should I do this? And is it a good practice to use AutoMapper for display?
Below is the code sample:
public class BookController : BaseController
{
private IBookService bookService;
public BookController(IBookService bookService)
{
this.bookService = bookService;
}
public ActionResult Details(int id)
{
var book = bookService.GetBookById(id);
return View(Mapper.Map<BookView>(book));
}
}
public class Book
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class BookView
{
public int Id { get; set; }
public string Name { get; set; }
}
If I use another way, I can customise any property, like below:
public ActionResult Details(int id)
{
var book = bookService.GetBookById(id);
return View(new BookView(book));
}
public class BookView
{
public BookView(Book book){
Name = book.Name +" Decorated";
}
public int Id { get; set; }
public string Name { get; set; }
}
How should I do this? And is it a good practice to use AutoMapper for display?
Update
It seems using automapper in the scenario below is more appropriate. For example, mapping a view model to domain model like below. Any opinions?
[HttpPost]
public ActionResult Create(BookView bookView)
{
try
{
var book = Mapper.Map<Book>(bookView); //this is wrong
bookService.SaveOrUpdate(book);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Update 2
For complex custom display via view model, I don't want to use automapper to map display logic, assuming automapper can map it. Because it mixes different purposes. For example:
Mapper.CreateMap<Book, BookView>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name + " this is for display purpose"));
However, using manual mapping like below seems intuitive.
public BookView(Book book){
//mapping here
}
Update 3
Quote from Jimmy Bogard:
I think using AutoMapper because you don’t want to use the “=”
operator is a bit lazy. Instead, we use it to flatten and reshape,
optimizing for the destination type’s environment. Remember, my
original motivation for AutoMapper was:
Enable protecting the domain layer from other layers by mapping to
DTOs
Thanks #AndrewWhitaker for the link
This is a good use case for AutoMapper (I've used it this way extensively on many projects with success). Generally you do not want to expose domain entities to your view (in MVC, this would be exposing your model directly to your view, which is incorrect).
You do not need a 1-1 mapping between domain entity and viewmodel. You can make them look completely different and customize the mapping in your CreateMap<> call. To use your example:
Mapper.CreateMap<Book, BookView>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name + " Decorated"));
Worst case, you can ditch automapper for complex cases or use a custom type resolver with automapper to get the job done.
In fact, this is how Jimmy Bogard (the author) recommends using AutoMapper. He specifically mentions mapping from domain entities to ASP.NET MVC ViewModels for use with strongly typed views.
Another advantage is that you can unit test your mapping profiles. This way if you end up with a mismatch between ViewModel and Model you'll get a failing unit test.
Updates:
I think the quote you added to your question further supports using AutoMapper for mapping from domain models to ViewModels:
Instead, we use it to flatten and reshape, optimizing for the destination type’s environment.
So in my example you'd definitely be optimizing for the destination type's environment (in this case a view).
Also per the link I reference above you should not be using automapper to map to the domain, only from. With that in mind, you'll have to write some logic to create/update domain entities from what you receive from the View no matter what. Remember that controller actions should not take domain entities directly (you should not trust data that comes directly from the view--let the model determine if a domain entity would be valid or not).

How to combine MVC4 with EF5 Db First separate project

In my solution I have two projects. One is the main MVC4 project. The other is a DataLayer project which contains an Entity Framework 5 edmx model generated from an existing DB (and maybe some Repositories later).
The problem is that the pocos EF5 generates sits in the DataLayer project. But I need them inside the Models folder in the MVC4 project.
I want the seperate DataLayer project to increase abstraction and separation of concerns, but I can't figure out how to put those two pieces together.
(I thought to maintain another layer of pocos in the Models folder but this dose not seems right)
I have my projects separated into two as you describe.
I thought to maintain another layer of pocos in the Models folder but this dose not seems right
I think you will find you will build this layer eventually.
Here's two projects Project.Data and Project.Web. Project.Web has a project reference to Project.Data.
Project.Data.Models: Entities
Project.Web.Models: DTOs, ViewModels
My views never directly reference Entities. I will map Entities to DTOs or ViewModels using AutoMapper. This happens in my services which sits in Project.Web under its own namespace. My services never return Entity types and my views use only ViewModels.
interface IFooService
{
FooDTO GetFoo(int id);
}
public class FooService : IFooService
{
public FooDTO GetFoo(int id)
{
var foo = dbContext.Foo.Where(f => f.Id == id).Select(f => new FooDTO {
Bar = f.Bar,
Blah = f.Blah
}).FirstOrDefault();
// I let AutoMapper take care of the mapping for me
var foo = Mapper.Map<FooDTO>(dbContext.Foo.Where(f => f.Id == id).FirstOrDefault());
return foo;
}
}
Controller Action:
public ActionResult FooDetails(int id)
{
FooViewModel foo = Mapper.Map<FooViewModel>(fooService.GetFoo(id));
return View(foo);
}
Edit:
Added anther model layer to map Entity => DTO => View Model
This is the job of the repository. Create DTO classes to hold view friendly models and use the repository to call your data layer and assemble the dto. The dtos can then be built specifically for being returned to the client, including any serialization or display decorations, etc. Nothing complicated here.
I think some people's first reaction is "I'm duplicating my effort if I have to create these classes" but you're really not as these classes serve a different purpose which is exactly what you're saying, separation of concerns.
public MyViewModel // model that is bound to the view
{
private UserRepository _userRepo;
public EmployeeDto ActiveUser {get;set;}
public MyViewModel()
{
_userRepo = new UserRepository();
LoadActiveUser();
}
private void LoadActiveUser()
{
var userId = (int)HttpContext.Current.Session["activeUser"] ?? 0;
if(userId > 0)
{
ActiveUser = _userRepo.GetEmployee(userId);
}
}
}
public UserRepository
{
private SomeEntityReference1 _myDal1;
private SomeEntityReference2 _myDal2; // maybe you need to make some other data layer call in order to fill this object out
public UserRepository()
{
_myDal1 = new SomeEntityReference1 ();
_myDal2 = new SomeEntityReference2 ();
}
public EmployeeDto GetEmployee(int id)
{
var empDto = new EmployeeDto();
// get employee
var dalEmpResult = _myDal.Employees.FirstOrDefault(e => e.EmployeeId == id);
empDto.FirstName = dalResult.FName;
empDto.LastName = dalResult.LName;
empDto.Id = dalResult.EmployeeId;
// get employee department info
var dalDeptResult = _myDal2.Departments.FirstOrDefault(d => e.DepartmentId == dalEmpResult.DeptartmentId);
empDto.DepartmentName = dalDeptResult.Name;
return empDto;
}
}
// client friendly employee object
[DataContract(Name="Employee")]
public class EmployeeDto
{
public int Id {get; internal set;}
[DataMember(Name="fname")]
[DisplayName("Employee First Name:")]
public string FirstName {get;set;}
[DataMember(Name="lname")]
[DisplayName("Employee Last Name:")]
public string LastName {get;set;}
public int DeptId {get;set;}
[DataMember(Name="dept")]
[DisplayName("Works at:")]
public string DepartmentName {get;set;}
}
The only reason I show two different EF references here (your database entity schemas) is just to illustrate that this would be your opportunity to do any "additional" processing before returning a FINISHED dto, ready for consumption.

ASP.net MVC - Should I use AutoMapper from ViewModel to Entity Framework entities?

I am currently using AutoMapper to map my Entity Framework entities to my View Model:
public class ProductsController : Controller
{
private IProductRepository productRepository;
public ProductsController(IProductRepository productRepository)
{
this.productRepository = productRepository;
}
public ActionResult Details(int id)
{
var product = productRepository.GetProduct(id);
if( product == null )
return View("NotFound");
ProductDetailsViewModel model = Mapper.Map<Product, ProductDetailsViewModel>(product);
return View(model);
}
}
This works well. The question I have is when I need to go from my View Model to my entity in order to update the database. Should I be using AutoMapper for this? Is this a bad/dangerous practice?
It seems like AutoMapper is good for flattening a complex type to a simple (flat) type, but so far I'm struggling trying to go from a flat/simple to a more complex type like my entity with the various navigation properties.
If it is a bad idea to use AutoMapper to do this, then what would my code look like for a Create action?
public ActionResult Create(CreateProductViewModel model)
{
if( ModelState.IsValid )
{
// what do i do here to create my Product entity?
}
}
What about an Edit action?
public ActionResult Edit(int id, EditProductViewModel model)
{
Product product = productRepository.GetProduct(id);
// how do i convert my view model to my entity at this point???
}
I'm a of the mindset that updating your entities is a pretty big deal and that no automated tool should ever be used. Set the properties manually.
Yes its a very tiny amount of more code but automapper or running updatemodel on database entities can sometimes have unintended consequences. Better to make sure your writes are done correctly.
I use AutoMapper with a specialized mapping class that understands how to make a complex model from a simple one. AutoMapper is used to handle the one-to-one mapping and the custom logic in the class for doing the more complex things (like relationships, etc.). All of the AutoMapper configuration is done in the static constructor for the mapping class, which also validates the mapping configuration so that errors fail early.
public class ModelMapper
{
static ModelMapper()
{
Mapper.CreateMap<FooView,Foo>()
.ForMember( f => f.Bars, opt => opt.Ignore() );
Mapper.AssertConfigurationIsValid();
}
public Foo CreateFromModel( FooView model, IEnumerable<Bar> bars )
{
var foo = Mapper.Map<FooView,Foo>();
foreach (var barId in model.BarIds)
{
foo.Bars.Add( bars.Single( b => b.Id == barId ) );
}
return foo;
}
}
You could also try configuring AutoMapper to only map Scalar properties (instead of having to .Ignore() every single property you don't want it to (including inherited properties like .EntityKey and .EntityState).
AutoMapper.Mapper.CreateMap<EntityType, EntityType>()
.ForAllMembers(o => {
o.Condition(ctx =>
{
var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping
if (!members.Any())
return false;
return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set
});
});
Some more info at http://www.prosoftnearshore.com/blog/post/2012/03/14/Using-AutoMapper-to-update-Entity-Framework-properties.aspx
Essentially automapping is bad, I wrote a blog post on this http://blog.gavryli.uk/2015/12/02/why-automapping-is-bad-for-you/

Categories

Resources