The profile:
public class StudentProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Student, StudentIndexModel>();
}
}
And the line it breaks on:
public ActionResult Index()
{
// Breaks here
var students = Mapper.Map<IEnumerable<StudentIndexModel>>(db.Students.ToList());
return View(students);
}
I'm getting an unsupported mapping exception when it gets records from the database. No exception when there are no records returned (the table has no records)
Error
Missing type map configuration or unsupported mapping.
Mapping types:
Student -> StudentIndexModel
ContosoUniversity.Models.Student -> ContosoUniversity.ViewModels.StudentIndexModel
Destination path:
IEnumerable`1[0]
The student class:
public class Student : Entity
{
public Student() { }
public string LastName { get; set; }
public string Forenames { get; set; }
public DateTime EnrolmentDate { get; set; }
public virtual ICollection<Enrolment> Enrolments { get; set; }
}
And the StudentIndexModel class:
public class StudentIndexModel
{
public int Id { get; set; }
public string LastName { get; set; }
public string Forenames { get; set; }
public DateTime EnrolmentDate { get; set; }
}
Where am I going wrong?
I've done it again, I find the answer to my own question 2 seconds later.
I added this to the Global.asax:
AutoMapperConfiguration.Configure();
Where AutomapperConfiguration has the Mapper.Initialize() method
Related
I have an Asp.Net web API project. And I'm implementing a shopping cart. So I have models that have to be converted to Dtos. So I'm using automapper to map Models to Dtos so I can transfer data but for some reason, I'm getting the following error.
The error message I get:
"Error mapping types.\r\n\r\nMapping types:\r\nCartModel -> CartDto\r\nMoby.Services.ShoppingCart.API.Models.CartModel -> Moby.Services.ShoppingCart.API.Models.Dto.CartDto\r\n\r\nType Map configuration:\r\nCartModel -> CartDto\r\nMoby.Services.ShoppingCart.API.Models.CartModel -> Moby.Services.ShoppingCart.API.Models.Dto.CartDto\r\n\r\nDestination Member:\r\nCartDetails\r\n"
Automapper config class:
public class MapperConfig
{
public static MapperConfiguration RegisterMaps()
{
return new MapperConfiguration(config =>
{
config.CreateMap<CartModel, CartDto>().ReverseMap();
config.CreateMap<CartHeaderModel, CartHeaderDto>().ReverseMap();
config.CreateMap<CartDetailsModel, CartDetailsDto>().ReverseMap();
config.CreateMap<ProductModel, ProductDto>().ReverseMap();
});
}
}
Automapper dependency injection:
var mapper = MapperConfig.RegisterMaps().CreateMapper();
builder.Services.AddSingleton(mapper);
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
CartModel:
public class CartModel
{
public CartHeaderModel CartHeader { get; set; }
public IEnumerable<CartDetailsModel> CartDetails { get; set; }
}
CartDto:
public class CartDto
{
public CartHeaderDto CartHeader { get; set; }
public IEnumerable<CartDetailsDto> CartDetails { get; set; }
}
CartDetailsModel:
public int Id { get; set; }
public int CartHeaderId { get; set; }
[ForeignKey(nameof(CartHeaderId))]
public virtual CartHeaderModel CartHeader { get; set; }
public int ProductId { get; set; }
[ForeignKey(nameof(ProductId))]
public virtual ProductModel Product { get; set; }
public int Count { get; set; }
CartDetailsDto
public class CartDetailsDto
{
public int Id { get; set; }
public int CartHeaderId { get; set; }
public virtual CartHeaderDto CartHeader { get; set; }
public int ProductId { get; set; }
public virtual ProductModel Product { get; set; }
public int Count { get; set; }
}
Define your mapping inside a class that extends Profile class.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<CartModel, CartDto>().ReverseMap();
CreateMap<CartHeaderModel, CartHeaderDto>().ReverseMap();
CreateMap<CartDetailsModel, CartDetailsDto>().ReverseMap();
CreateMap<ProductModel, ProductDto>().ReverseMap();
}
}
Remove these lines:
var mapper = MapperConfig.RegisterMaps().CreateMapper();
builder.Services.AddSingleton(mapper);
Only keep this line:
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
AddAutoMapper will scan the assemblies for any classes that extend Profile and load the mapping configuration from them. I will also register a mapper that you can inject in your services (IMapper).
I am using GraphQL as a part of my CQRS application.
I have separated Command and Query domain model into two different projects (Query and Command).
each of there projects has some models for instance Student model.
I use two different model in Domain.Query.Students.Student and Domain.Command.Students.Student, but both have the same entity name (Student and Address)
I simplified my entity as below:
public class Student:Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
public DateTimeOffset CreatedOn { get; set; }
public DateTimeOffset ModifiedOn { get; set; }
}
Student is being used in Query and Command projects.
in Startup.cs I have configured GraphQL like this:
services
.AddGraphQLServer()
.AddQueryType<StudentQuery>()
.AddMutationType<AddStudentMutation>();
In StudentQuery.cs
namespace GraphQL.Query.Students
{
public class StudentQuery
{
public List<Student> AllStudents([ScopedService] StudentRepository studentRepository)
{
var query = studentRepository.GetEmployees();
return query;
}
}
}
and AddStudentMutation.cs
namespace GraphQL.Command.Students
{
public class AddStudentMutation
{
public async Task<Student> AddStudentAsync(AddStudentInputType student,[Service] IMediator mediator)
{
var result =await mediator.Send(new AddStudentCommand(student.FirstName,student.LastName,student.StreetAddress,
student.City,student.State,student.ZipCode));
return result;
}
}
[GraphQLName("AddStudentInput")]
public class AddStudentInputType
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
}
When I run the application I get the following error:
The name `Student` was already registered by another type. (HotChocolate.Types.ObjectType<Domain.Query.Students.Student>)
If I change the Student entity in Domain.Query.Students.Student to ReadStudent or something else, it will run without any issue, but I don't want to change it.
You can add the GraphQL annotation onto the Student class as [GraphQLName("StudentQuery")]
I recently implemented OData in my ASP .NET Core web API. I have found success as long as I am returning the database models directly. I run into trouble, however, as soon as I attempt to return domain models instead.
The underlying issue involves mapping a data class to a domain class while maintaining the IQueryable return type. While I have found partial success using AutoMapper's MapTo extension method, I find that I am unsuccessful when using the $extend method to expand a collection of entities that are also domain objects.
I have created a sample project to illustrate this issue. You may view or download the full project on github here. See the description below.
Given the following two database classes:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
public Product() {
Orders = new Collection<Order>();
}
}
public class Order
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the following domain models...
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<OrderEntity> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the Products Controller
public class ProductsController
{
private readonly SalesContext context;
public ProductsController(SalesContext context) {
this.context = context;
}
[EnableQuery]
public IQueryable<ProductEntity> Get() {
return context.Products
.ProjectTo<ProductEntity>()
.AsQueryable();
}
}
All the following OData queries Pass:
http://localhost:51004/odata/Products
http://localhost:51004/odata/Orders
http://localhost:51004/odata/Orders?$expand=Products
The following query, however, does not pass:
http://localhost:51004/odata/Products?$expand=Orders
An HTTP response is never returned. The only failure message I get comes from the console:
System.InvalidOperationException: Sequence contains no matching element at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
Finally, here is a reference to the mapping profile:
public static class MappingProfile
{
public static void RegisterMappings() {
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Order, OrderEntity>();
cfg.CreateMap<Product, ProductEntity>();
});
}
}
I can solve the issue by simply returning a List instead of an IEnumerable in the controller, but this of course would trigger a large query against the database that would be performance intensive.
As stated above, you can find a link to the full project on Github here. Let me know if you find any answers!
I was able to get this working with a few small revisions.
Updating the domain models:
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
Manually enabling expansion on the route builder:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SalesModelBuilder modelBuilder)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select();
routeBuilder.MapODataServiceRoute("ODataRoutes", "odata",
modelBuilder.GetEdmModel(app.ApplicationServices));
});
}
Using the following Queries:
http://localhost:51004/odata/Products
http://localhost:51004/odata/Orders
http://localhost:51004/odata/Orders?$expand=Product
http://localhost:51004/odata/Products?$expand=Orders
I have the following scenario. We need to be able to fill forms for some tables, examples Companies (Empresa in Spanish), however we want the administrator to be able to extend the entity itself with additional fields.
I designed the following classes, and I need to seed at least one row, however its unclear to me how to seed one row of type CampoAdicional
Entity class:
public abstract class Entidad
{
[Key]
public int Id { get; set; }
}
Company Class (Empresas)
public class Empresa : Entidad
{
public string Nombre { get; set; }
public string NIT { get; set; }
public string NombreRepresentanteLegal { get; set; }
public string TelefonoRepresentanteLegal { get; set; }
public string NombreContacto { get; set; }
public string TelefonoContacto { get; set; }
public virtual ICollection<CampoAdicional> CamposAdicionales { get; set; }
}
And the Additional Fields (Campo Adicional)
public class CampoAdicional
{
[Key]
public int Id { get; set; }
public string NombreCampo { get; set; }
public virtual Tiposcampo TipoCampo { get; set; }
public virtual Entidad Entidad { get; set; }
}
However I dont know how to seed this class or table, because entity should be of subtype Company
Obviously the typeof doesnt compile
context.CampoAdicionals.Add(new CampoAdicional() { Entidad = typeof(Empresa), Id = 1, NombreCampo = "TwitterHandle", TipoCampo = Tiposcampo.TextoUnaLinea });
Update 1: Please note that the additional fields are for the entire entity company not for each company.
Unfortunately, I don't think you'll be able to use EF to automatically create that kind of relationship. You might be able to do something similar with special getters and such:
public class Entidad
{
// stuff...
public IEnumerable<CampoAdicional> CamposAdicionales
{
get { return CampoAdicional.GetAll(this); }
}
}
public class CampoAdicional
{
[Key]
public int Id { get; set; }
public string NombreCampo { get; set; }
public virtual Tiposcampo TipoCampo { get; set; }
protected string EntidadType { get; set; }
// You will need some mapping between Type and the EntidadType string
// that will be stored in the database.
// Maybe Type.FullName and Type.GetType(string)?
protected Type MapEntidadTypeToType();
protected string MapTypeToEntidadType(Type t);
[NotMapped]
public Type
{
get { return MapEntidadTypeToType(); }
// maybe also check that Entidad.IsAssignableFrom(value) == true
set { EntidadType = MapTypeToEntidadType(value); }
}
public static IEnumerable<CampoAdicional> GetAll(Entidad ent)
{
return context.CampoAdicionals
.Where(a => a.EntidadType == MapTypeToEntidadType(ent.GetType()));
}
}
I am having an issue where automapper is trying to map a property that I do not want it to map. I can fix this issue by renaming the property to something else but I want to use the name and I want to know how to fix issues like this.
My code looks like this.
public abstract class Task
{
public int Id { get; set; }
public int ProjectId { get; set; }
public virtual Project Project { get; set; }
public string Title { get; set; }
}
public class BasicTask : Task
{
}
public abstract class Project
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
[NotMapped]
public abstract string ProjectTypeDescription { get; }
}
public class BasicProject : Project
{
public override string ProjectTypeDescription
{
get { return "Basic Project"; }
}
}
public abstract class TaskViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public abstract string TaskType { get; }
//This is the property giving me issues
public ProjectDetails Project { get; set; }
public class ProjectDetails
{
public int Id { get; set; }
public string Title { get; set; }
}
}
public class BasicTaskViewModel : TaskViewModel
{
public override string TaskType
{
get { return "Basic"; }
}
}
public abstract class ProjectViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public SelectList ProjectTypes { get; set; }
public abstract string ProjectType { get; }
public IEnumerable<TaskViewModel> Tasks { get; set; }
}
public class BasicProjectViewModel : ProjectViewModel
{
public override string ProjectType
{
get { return "Basic Project"; }
}
}
My mapping for Tasks looks like (I removed the project mapping since I can replicate the problem without those mappings)
Mapper.CreateMap<Task, TaskViewModel>()
.ForMember(dest => dest.Project, opt => opt.Ignore())
.Include<BasicTask, BasicTaskViewModel>();
Mapper.CreateMap<BasicTask, BasicTaskViewModel>();
Mapper.CreateMap<TaskViewModel, Task>()
.Include<BasicTaskViewModel, BasicTask>();
Mapper.CreateMap<BasicTaskViewModel, BasicTask>();
Mapper.CreateMap<Project, TaskViewModel.ProjectDetails>();
I am use an extenstion method for the mapping
public static TResult MapTo<TResult>(this object self)
{
return (TResult)Mapper.Map(self, self.GetType(), typeof(TResult));
}
An example of using this that fails is
TaskViewModel vm = new BasicTaskViewModel()
{
Id = 1,
Project = new TaskViewModel.ProjectDetails()
{
Id = 1,
Title = "Some Title",
}
};
Task m = vm.MapTo<Task>();
Sorry for the very long post I just don't know where the problem is.
If I rename Project in the taskviewmodel to something else it works and doesn't map TaskViewModel.ProjectDetails to Project which is what I want.
I have tried adding ForSourceMember(src => src.Project, opt => opt.Ignore()) in every spot that I can
When I run this I get the follwoing
Missing type map configuration or unsupported mapping.
Mapping types:
ProjectDetails -> Project
amtest.ViewModel.TaskViewModel+ProjectDetails -> amtest.Models.Project
Destination path:
BasicTask.Project.Project
Source value:
amtest.ViewModel.TaskViewModel+ProjectDetails
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: AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
ProjectDetails -> Project
amtest.ViewModel.TaskViewModel+ProjectDetails -> amtest.Models.Project
Destination path:
BasicTask.Project.Project
Source value:
amtest.ViewModel.TaskViewModel+ProjectDetails
Does it help when you add the ignore also to the mapping of the child class?
Mapper.CreateMap<BasicTaskViewModel, BasicTask>()
.ForMember(dest => dest.Project, opt => opt.Ignore());