i have implemented simple MVC3 application in which i have used AutoMapper to bind my database table to ViewMode, but i unable to bind complex ViewModel using automapper.
this is my domain class
namespace MCQ.Domain.Models
{
public class City
{
public City()
{
this.AreaPincode = new List<AreaPincode>();
}
public long ID { get; set; }
public string Name { get; set; }
public int DistrictID { get; set; }
public virtual ICollection<AreaPincode> AreaPincode { get; set; }
public virtual District District { get; set; }
}
}
my ViewModel class
public class CityViewModel
{
public CityViewModel()
{
this.AreaPincode = new List<AreaPincodeViewModel>();
}
public long ID { get; set; }
public string Name { get; set; }
public int DistrictID { get; set; }
public ICollection<AreaPincodeViewModel> AreaPincode { get; set; }
}
in that i have one ICollection property when i trying to map this property it show me following error
The following property on MCQ.ViewModels.AreaPincodeViewModel cannot be mapped:
AreaPincode
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type MCQ.ViewModels.AreaPincodeViewModel.
Context:
Mapping to property AreaPincode from MCQ.Domain.Models.AreaPincode to MCQ.ViewModels.AreaPincodeViewModel
Mapping to property AreaPincode from System.Collections.Generic.ICollection`1[[MCQ.Domain.Models.AreaPincode, MCQ.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] to System.Collections.Generic.ICollection`1[[MCQ.ViewModels.AreaPincodeViewModel, MCQ, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Mapping from type MCQ.Domain.Models.City to MCQ.ViewModels.CityViewModel
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
following code stuff i have written in my Global.asax
Mapper.CreateMap<City, CityViewModel>()
.ForMember(s => s.DistrictID, d => d.Ignore())
.ForMember(s => s.AreaPincode, d => d.MapFrom(t => t.AreaPincode));
please let me know how i should bind this custom collection property by using automapper.
You need to create a custom mapping between AreaPincode and AreaPincodeViewModel:
Mapper.CreateMap<AreaPincode, AreaPincodeViewModel>()
.ForMember(...)
And there is no need in this row: .ForMember(s => s.AreaPincode, d => d.MapFrom(t => t.AreaPincode)) it will be matched automatically
When mapping to AreaPincode in the CityViewModel you need to convert from the type ICollection<AreaPincode> to ICollection<AreaPincodeViewModel>, i.e. map all elements of type AreaPincode to elements of type AreaPincodeViewModel.
To do this, create a new mapping from AreaPincode to AreaPincodeViewModel.
Mapper.CreateMap<AreaPincode, AreaPincodeViewModel>()
...
Once this is in place, AutoMapper should take care of the rest. You won't even need the line
.ForMember(s => s.AreaPincode, d => d.MapFrom(t => t.AreaPincode));
because AutoMapper will figure this mapping out automagically, as the property names are equal.
Related
I did some research but I couldn't find exactly what I wanted.
I have an endless menu. I have a MenuDTO and a MenuViewModel that I use for this menu. I had no problem matching between model and DTO, but am having trouble mapping DTO to ViewModel. Obviously I couldn't find the solution, can you help?
My MenuDTO Object
public class MenuDto : BaseDto
{
public string Name { get; set; }
public string Icon { get; set; }
public string Order { get; set; }
public string Url { get; set; }
public bool IsVisible { get; set; }
public int ParentId { get; set; }
public MenuDto ParentMenu { get; set; }
public List<MenuDto> Menus { get; set; }
}
And MenuViewModel
public class MenuViewModel
{
public int Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Order { get; set; }
public string Url { get; set; }
public bool IsVisible { get; set; }
public int ParentId { get; set; }
public MenuViewModel ParentMenu { get; set; }
public List<MenuViewModel> Menus { get; set; }
}
This is how I mapped the MenuDTO and MenuViewModel objects.
public class WebProfile : Profile
{
public WebProfile()
{
CreateMap<MenuDto, MenuViewModel>();
CreateMap<MenuViewModel, MenuDto>();
}
}
I call this way in the controller
var navMenuItems = _mapper.Map<List<MenuViewModel>(_menuService.GetNavMenus());
Although all fields are mapped, I get an error on the Menus field.
The error message I get is;
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
MenuDto -> MenuViewModel
BiPortal2020.Business.ServiceDTOs.Menu.MenuDto -> BiPortal2020.WebUI.Areas.Admin.Models.Menu.MenuViewModel
lambda_method(Closure , MenuDto , MenuViewModel , ResolutionContext )
AutoMapperMappingException: Error mapping types.
Mapping types:
Object -> List`1
System.Object -> System.Collections.Generic.List`1
The error message implies - AutoMapper, either cannot map between MenuDto and MenuViewModel, or it cannot locate the defined mappings.
I've tested your mappings and they are totally fine. So, what possibility remains is AutoMapper cannot locate your mappings.
I'm Assuming the Business Layer and UI Layer you mentioned in the comment section are two separate projects. Since the WebProfile is defined in the UI Layer, you have to tell AutoMapper that it should search that assembly to find the mappings. Since your mappings between Models and DTOs are working, I can guess you've already done the same for BusinessProfile which is defined in the Business Layer.
I don't know about your existing code, but you could do something like this - in the Startup.Configure method add/modify the following line -
services.AddAutoMapper(typeof(IDtoMapping), typeof(IViewModelMapping));
where IDtoMapping and IViewModelMapping are two marker interface (empty interface, used only to identify the assembly they are declared in) declared in the Business Layer and UI Layer, respectively.
Note: Technoligies in use are ASP.Net MVC 3, Entity, SQL Server Management Studio
Problem?
It seems that when I run, the context as: public class DatabaseInit : DropCreateDatabaseAlways<LocationAppContext>
That it creates the database, but my service assignments table has an extra foreign key called
ServiceAssignment_Service when it shouldn't.
My service assignment model is as such:
namespace LocationApp.Models
{
public class ServiceAssignment
{
public int id { get; set; }
public int locationID { get; set; }
public int ServiceID { get; set; }
public virtual Location Location { get; set; }
public virtual ServiceAssignment Service { get; set;}
}
}
and the service model is as such:
namespace LocationApp.Models
{
public class Service
{
public Service()
{
this.ServiceAssignments = new HashSet<ServiceAssignment>();
}
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public bool active { get; set; }
public string icon { get; set; }
public virtual ICollection<ServiceAssignment> ServiceAssignments { get; set; }
}
}
with that said, the relation ship is simple:
service assignments have many location id's and service id's.
why is this extra foriegn key being generated? the curent keys, that should e there is:
PK: Main PK for the table
FK 1: Location_ServiceAssignment
FK 2: Service_ServiceAssignment
Those are their, how ever this third one is baffling....
The second part is: If a location of id 2 has a service id of 2,3,6,7 How do I get all service id's returned, such that I can pass the object to a service query to get all information on the service based on the ID?
Update:
Context Class:
namespace LocationApp.DAL
{
public class LocationAppContext : DbContext
{
public DbSet<Content> Contents { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<ServiceAssignment> ServiceAssignments { get; set; }
public DbSet<Service> Services { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Location>().HasMany(sa => sa.ServiceAssignments);
modelBuilder.Entity<Service>().HasMany(sa => sa.ServiceAssignments);
}
}
}
I think you have to tell EF that Service.ServiceAssignments is the inverse navigation property of ServiceAssignment.Service and that Location.ServiceAssignments is the inverse of ServiceAssignment.Location. Right now with your mapping you only specify that Location or Service has many ServiceAssignments. EF will consider the navigation properties in ServiceAssignment as the ends of separate relationships.
Try instead the mapping:
modelBuilder.Entity<Location>()
.HasMany(l => l.ServiceAssignments)
.WithRequired(sa => sa.Location)
.HasForeignKey(sa => sa.LocationID);
modelBuilder.Entity<Service>()
.HasMany(s => s.ServiceAssignments)
.WithRequired(sa => sa.Service)
.HasForeignKey(sa => sa.ServiceID);
You can probably remove this mapping altogether as an alternative because EF should detect the right relationships by convention.
So, use either no mapping (=mapping by convention) or the full mapping (=specifying both ends of the relationships). Just the 50%-mapping you have used is likely the problem.
Code
I will show you the code and then explain the problem
Interfaces
public interface IUser
{
Guid ID { get; set; }
string Name { get; set; }
ICollection<IRole> Roles { get; set; }
}
public interface IRole
{
Guid ID { get; set; }
string Name { get; set; }
}
Notice that the interface IUser define a collection Roles of type IRole
Implementation
public class Role : IRole
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class User : IUser
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<IRole> Roles { get; set; }
}
EF Fluent API Configuration
public class RoleConfiguration : EntityTypeConfiguration<Role>
{
public RoleConfiguration()
{
HasKey(p => p.ID)
.Property(p => p.ID);
Property(p => p.Name)
.IsRequired()
.HasMaxLength(70);
}
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(p => p.ID)
.Property(p => p.ID)
.IsRequired();
Property(p => p.Name)
.HasMaxLength(60)
.IsRequired();
HasMany(r => r.Roles).WithMany();
}
}
Note that the configuration EntityTypeConfiguration where T is the implementation and not the interface (the EF does not allow to put the interface as T)
Problem
#1 situation:
If you run the application, to generate the relational model, the following error occurs:
The navigation property 'Roles' is not a declared property on type 'User'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The navigation property 'Roles' is not a declared property on type 'User'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Source Error:
Line 58: public IQueryable<Project> GetAll(int pageIndex, int pageSize, params Expression<Func<Project, object>>[] includeProperties)
Line 59: {
Line 60: return includeProperties.Aggregate<Expression<Func<Project, object>>,
Line 61: IQueryable<Project>>(Context.Projects, (current, includeProperty) => current.Include(includeProperty)).OrderBy(p => p.Name).Skip(pageIndex).Take(pageSize);
Line 62: }
#2 situation:
If you comment out the line HasMany(r => r.Roles).WithMany(); EF will generate the relational model with no relationship between User and Role (which should be many to many)
I believe this is because the User class, there is a collection type ICollection<IRole> and not of kind ICollection.
Question
The question is, how to solve this problem?
How to map the collection public ICollection<IRole> Roles { get; set; } using Fluent API EF
CodeFirst doesn't support mapping interfaces. You will need to change it to reference the Role concrete class.
Here's how I would implement your interfaces in order to work around the problem that Betty described.
public class Role : IRole
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class User : IUser
{
public Guid ID { get; set; }
public string Name { get; set; }
// Explicit implementation of the IUser.Roles property.
// Exists to satisfy the IUser interface but is not normally used.
// Used only when the client code types this object the interface, IUser.
// Otherwise ignored.
ICollection<IRole> IUser.Roles
{
get { return this.Roles as ICollection<IRole>; }
set { this.Roles = value as ICollection<Role>; }
}
// This property will be used in most cases.
// Used when the client code types this object as the concrete type, User.
public ICollection<Role> Roles { get; set; }
}
I am not sure if this is possible but here is my situation.
Say I have a model like this:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
My View model looks like this:
public class ProductModel
{
public int Id { get; set; }
public string Name { get; set; }
public string CustomViewProperty { get; set; }
}
I am using my ProductModel to post back to a form and I don't care or need the Custom View Property. This mapping works fine as automapper drops the unknown properties.
What I would like to do is map my custom properties in only one direction. i.e.
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty //???This is where I am stuck
What ends up happening is when I call "ToModel", automapper dumps my unknown properties and nothing comes over the wire.
Like this.
var product = _productService.GetProduct();
var model = product.ToModel;
model.CustomViewProperty = "Hello World"; //This doesn't go over the wire
return View(model);
Is this possible? Thanks.
You should ignore unmapped properties:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.Ignore());
or map them:
Mapper.CreateMap<Product, ProductModel>()
.ForMember(dest => dest.CustomViewProperty, opt=>opt.MapFrom(product=>"Hello world"));
I am trying to figure out how to use Automapper when my entity has a field of type entity.
I've got 3 classes like these:
public abstract class Entity<IdK>
{
public virtual IdK Code { get; protected set; }
}
public class Contact : Entity
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
}
public class Company : Entity
{
public virtual string Name { get; set; }
}
My class Contact contain an element of type Company.
I've also created a ViewModel to trasfer some infos to my view:
public ContactViewModel()
{
public Guid Code { get; set; }
public int Version { get; set; }
[DisplayName("Contact")]
public string Name { get; set; }
[DisplayName("Company")]
public string Company { get; set; }
}
In my viewmodel I've defined a field Company of type string. This is going to contain a value the user will chose from a dropdown (list of companies).
I've defined a bootstrapper when my App starts:
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x => {
x.CreateMap<Domain.Contact, ViewModels.ContactViewModel>();
x.CreateMap<ViewModels.ContactViewModel, Domain.Contact>()
});
}
}
When I try to remap my ViewModel to my entity I get a conversion error (AutoMapper.AutoMapperMappingException).
Automapper can't figure out how to convert my Company (string) into an object of type Company, member of Contact.
Is it possible to define a rule so that Automapper know how to transform the string (company) into the code of my Company object, member of Contact?
You need to use a Resolver. Something like:
public class CompanyTypeResolver : ValueResolver<string, Company>
{
protected override Company ResolveCore(string name)
{
return new Company {Name = name};
}
}
Then in mapping code you call it like:
.ForMember(dto => dto.Company, opt => opt.ResolveUsing<CompanyTypeResolver>().FromMember(src => src.Name))