Automapping issue - c#

I am new to AutoMapper ✌ and struggling with the mapping of an array with values to a Entity Framework model.
It should be very simple but I get the exception for several hours right now.. (Missing type map configuration or unsupported mapping)
I just created a class automapper configuration class:
public static class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.CreateMap<Response<Car>, Car>()
.ForMember(dst => dst.Brand, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "brand").FirstOrDefault().Content))
.ForMember(dst => dst.Color, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "color").FirstOrDefault().Content))
.ForMember(dst => dst.BuildYear, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "buildyear").FirstOrDefault().Content))
.ForMember(dst => dst.Price, opt => opt.MapFrom(src => src.Content.Values.Where(p => p.Code == "price").FirstOrDefault().Content));
}
}
}
And I call this in my startup class..
AutoMapperConfiguration.Configure();
And this where the mapping should happen. (In a hangfire job)
Mapper.Map<Car>(response.Content.Values));
Is seems that the mapping configuration is unsupported, are there any alternatives to fix this issue?

Related

How to inject IConfiguration .net settings into Profile class?

So I'm my appsettings I have some string array filters that I need to use within mapping.
How can I pass them to MappingMethod below? (sample below does not work as MyProfile can't have parameters)
public class MyProfile: Profile
{
public MyProfile(IConfiguration configuration)
{
CreateMap<Source, Destination>()
.ForMember(dest => dest.X,
opt => opt.MapFrom(src => MappingMethod(src.NestedSource, configuration.GetSection("settingsArray")))
}
}

EF Core - Return mapped Many-to-Many relationship from OData using Automapper

Info
The app type is a Hosted Blazor web-assembly. And below are the versions of the nuget packages I am using. There is an error that occurs when trying to expand a navigation property that is a many-to-many relationship. The classes are mapped to DTO classes that flattens the middle relationship class.
.Net core Version="3.1"
AutoMapper Version="10.0.0"
AutoMapper.AspNetCore.OData.EFCore Version="2.0.1"
AutoMapper.Extensions.ExpressionMapping Version="4.0.1"
AutoMapper.Extensions.Microsoft.DependencyInjection Version="8.0.1"
Microsoft.AspNetCore.Components.WebAssembly.Server Version="3.2.1"
Microsoft.AspNetCore.OData Version="7.5.0"
To run this repo, you will need the free version of SQL Server or better
Set the EfCoreAutomapperOdata.Server project as the startup project and navigate to the Courses page (https://localhost:5001/courses) and click on either course. This will throw the following error:
System.InvalidOperationException: No generic method 'Include' on type 'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. at System.Linq.Expressions.Expression.FindMethod(Type type, String methodName, Type[] typeArgs, Expression[] args, BindingFlags flags)...
Models
See here - Entity Models and here - Dto Models for class definition
Automapper Config
public class AutomapperConfig : Profile
{
public AutomapperConfig()
{
CreateMap<Instructor, InstructorDto>();
CreateMap<InstructorDto, Instructor>();
CreateMap<Course, CourseDto>()
.ForMember(dto => dto.Students, opt => {
opt.MapFrom(_ => _.Students.Select(y => y.Student));
});
CreateMap<CourseDto, Course>()
.ForMember(ent => ent.Students, ex => ex
.MapFrom(x => x.Students.Select(y => new CourseStudent {
CourseId = x.Id,
StudentId = y.Id
})));
CreateMap<Student, StudentDto>()
.ForMember(dto => dto.Courses, opt => {
opt.MapFrom(x => x.Courses.Select(y => y.Course));
})
.ForMember(dto => dto.Friends, opt => {
opt.MapFrom(x => x.Friends.Select(y => y.Friend));
});
CreateMap<StudentDto, Student>()
.ForMember(ent => ent.Courses, ex => ex
.MapFrom(x => x.Courses.Select(y => new CourseStudent
{
StudentId = x.Id,
CourseId = y.Id
})));
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// ------ Some code removed for brevity ------
services.AddOData();
services.AddAutoMapper(cfg => { cfg.AddExpressionMapping(); },typeof(AutomapperConfig));
// ------ Some code removed for brevity ------
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ------ Some code removed for brevity ------
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Filter().OrderBy().Count().Expand().MaxTop(1000);
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
endpoints.MapFallbackToFile("index.html");
});
}
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<CourseDto>("Courses");
builder.EntitySet<InstructorDto>("Instructors");
builder.EntitySet<StudentDto>("Students");
return builder.GetEdmModel();
}
}
Course Controller
public class CourseController : ODataController
{
protected readonly BlazorContext _context;
protected readonly IMapper _mapper;
public CourseController(BlazorContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet]
[ODataRoute("Courses")]
public async Task<IActionResult> Get(ODataQueryOptions<CourseDto> options)
{
return Ok(await _context.Course.GetAsync(_mapper, options));
}
[HttpGet]
[ODataRoute("Courses({id})")]
public async Task<IActionResult> Get([FromODataUri] int id, ODataQueryOptions<CourseDto> options)
{
return Ok((await _context.Course.GetAsync(_mapper, options)).Where(s => s.Id == id).ToList());
}
}
Sample odata api query that fails
/odata/Courses?$expand=Students
Repro
I have built demo Blazor WASM app for this issue to reproduce
Repository
General Setup
To make expansions work, you need to allow for the $expand query option to be used. Configure it explicitly like so:
private IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<EstimateDto>(nameof(MyContext.Estimates))
.EntityType
.Expand(); // <-- allow expansion
builder.EntitySet<TypeDto>(nameof(MyContext.Types))
.EntityType
.Expand(); // <-- allow expansion
builder.EntitySet<SubTypeDto>(nameof(MyContext.SubTypes));
return builder.GetEdmModel();
}
You will also need to update your AutoMapper map, to allow the queries to be successfully mapped to a DTOs:
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
CreateMap<Estimate, EstimateDto>()
.ForMember(
dto => dto.Types,
opt => opt.MapFrom(x => x.EstimateTypes.Select(y => y.Type)));
// The following mapping is needed for expansion to work:
CreateMap<EstimateTypeRel, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(x => x.Type));
CreateMap<Type, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(x => x.SubTypes.Select(y => y.SubType)));
CreateMap<SubTypeRel, SubTypeDto>();
}
}
With that configuration being setup, there are at least two possible solutions to this issue, depending on your requirements:
A) Only expand Types
If you only want to expand Types, you will need to change your AutoMapper mappings by adding a .Where(z => z != null) clause, because as the exception tells you, null values are not allowed in collections, but OData includes them for the non-expanded SubType entities:
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
CreateMap<Estimate, EstimateDto>()
.ForMember(
dto => dto.Types,
opt => opt.MapFrom(
x => x.EstimateTypes.Select(y => y.Type)
.Where(z => z != null))); // <-- filter out null values
CreateMap<EstimateTypeRel, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(x => x.Type));
CreateMap<Type, TypeDto>()
.ForMember(
dto => dto.SubTypes,
opt => opt.MapFrom(
x => x.SubTypes.Select(y => y.SubType)
.Where(z => z != null))); // <-- filter out null values
CreateMap<SubTypeRel, SubTypeDto>();
}
}
Then you can use the following query:
https://localhost:5001/odata/Estimates(1)?$expand=Types
B) Also expand SubTypes
The alternative is to expand the SubTypes property as well, so the collection can be properly filled. To expand DTO mapped properties over multiple levels, use the $expand query option in your query string like so:
https://localhost:5001/odata/Estimates(1)?$expand=Types($expand=SubTypes)

Automapper: map child property with existing mapper using instance API

I'm trying to map one complex object to another using instance API:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Student, PersonType>();
cfg.CreateMap<Professor, PersonType>();
cfg.CreateMap<Branch, BranchType>()
.ForMember(x => x.Departments, opt => opt.MapFrom(src =>
new DepartmentType[] {
new DepartmentType
{
Students = Mapper.Map<Student[], PersonType[]> (src.Students),
Professors = Mapper.Map<Professor[], PersonType[]> (src.Professors),
Name = src.DepartmentName
}
}))
.ForMember(x => x.Name, opt => opt.MapFrom(src => src.Name))
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
});
var mapper = config.CreateMapper();
var test = mapper.Map<BranchType>(source);
The problem is I don't know how to achieve this without mixing instance and static API which is not working. Here is the error:
InvalidOperationException: Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
Apparently mixing of the static and instance based approaches is not allowed:
Students = Mapper.Map<Student[], PersonType[]> (src.Students)
How to use existing map to apply it to a property of the complex object with instance API?

Automapper inject DbContext in the Profile class

I have the following mapping profile
public class DomainProfile : Profile
{
private FootballPredictionsContext m_Context;
public DomainProfile(FootballPredictionsContext context)
{
m_Context = context;
}
public DomainProfile()
{
CreateMap<TipModel, Tip>()
.ForMember(tip => tip.BetType, m => m.MapFrom(x => m_Context.BetTypes.First(y => y.Name == x.BetType)))
.ForMember(tip => tip.BetCategory, m => m.MapFrom(x => m_Context.BetCategories.First(y => y.Name == x.BetCategory)))
.ForMember(tip => tip.Sport, m => m.MapFrom(x => m_Context.Sports.First(y => y.Name == x.Sport)))
.ForMember(tip => tip.Tipster, m => m.MapFrom(model => m_Context.Tipsters.First(y => y.Username == model.Tipster)));
}
}
As you can see, some of the mappings are using the DbContext, so I have to somehow inject it in the DomainProfile
In the Startup class I am initializing the Automapper normally
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IUnificator), typeof(Unificator));
services.AddDbContext<FootballPredictionsContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database")));
services.AddDbContext<UnificationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database")));
services.AddSingleton(provider => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new UserProfile(provider.GetService<IUserManager>()));
}).CreateMapper());
services.AddMvc();
}
I tried this solution, but I am receiving 'Cannot resolve scoped service 'FootballPredictions.DAL.FootballPredictionsContext' from root provider.'
I've come across a similar problem recently and it was down to the fact that I was trying to inject a service into a service with a longer lifetime (e.g. transient and scoped). What lifetime is associated with the DomainProfile class? Have you tried changing that to Scoped or Transient to see if that helps?
As implemented by #DimitarTsonev:
So, changing the mapper scope to
services.AddScoped(provider => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new DomainProfile(provider.GetService<FootballPredictionsContext>()));
}).CreateMapper());
fixed the issue
Assuming you've actually registered you context (services.AddDbContext, for example), the most likely cause of that exception is that you're registering AutoMapper before you're registering the context. Make sure your context is registered first, before you do something like services.AddAutoMapper.

How to combine mapping expressions during configuration for use in a Container

Given the following code:
public static IMapper ConfigureMapper()
{
var config = new MapperConfiguration(cfg => {
cfg.ShouldMapField = fi => false;
cfg.ShouldMapProperty = pi => pi.GetMethod != null && (pi.GetMethod.IsPublic || pi.GetMethod.IsVirtual);
cfg.CreateMap<ServiceModel.Types.NonRiskRequirement, RequiredSignature>()
.ForMember(dest => dest.ApplicantFlag, opt => opt.MapFrom(src => src.RequiredSignatureApplicantFlag))
.ForMember(dest => dest.InsuredFlag, opt => opt.MapFrom(src => src.RequiredSignatureInsuredFlag));
cfg.CreateMap<ServiceModel.Types.NonRiskRequirement, NonRiskWorkItem>()
.ForMember(dest => dest.RequiredSignature, opt => opt.MapFrom(
src => Mapper.Map<ServiceModel.Types.NonRiskRequirement, RequiredSignature>(src)))
.ForMember(dest => dest.WorkType, opt => opt.MapFrom(src => src.WorkType));
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
What is the proper syntax to get rid of the static "Mapper.Map"?, I'm using AutoMapper 5.1.1 with Autofac 4.1.1 and getting this exception back.
System.InvalidOperationException: Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
Thank you,
Stephen
Don't use MapFrom, use ResolveUsing. It gives you a ResolutionContext object that includes a Mapper property that you use to map, not this static way, which is not recommended, and also now broken.

Categories

Resources