AutoMapper - Get error when trying to map two classes - c#

I am trying to use AutoMapper to map a DTO to an Entity class but I keep getting an error.
Here is the DTO Class:
public class Product
{
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public PriceTiers PriceTiers { get; set; }
}
and here is the Entity:
public partial class Product
{
public Product()
{
PriceTiers = new List<PriceTiers>();
}
[Key]
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public virtual ICollection<PriceTiers> PriceTiers { get; set; }
}
Why do I keep getting the following error?
{"Missing type map configuration or unsupported
mapping.\r\n\r\nMapping types:\r\nPriceTiers ->
ICollection1\r\nWeb.Areas.DEAR.DTOs.PriceTiers -> System.Collections.Generic.ICollection1[[Web.Areas.DEAR.Data.PriceTiers,
Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]\r\n\r\n
Destination Member:\r\nPriceTiers\r\n"}
This is what I have in the Profile class:
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
and this is what I use to map the classes:
var products = _mapper.Map<IEnumerable<Product>>(result.Products);
This is what is in the Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);

The exception message is quite clear, the AutoMapper doesn't know how to map the data from DTOs.PriceTiers to ICollection<Data.PriceTiers>.
Solution 1: Map from DTOs.PriceTiers to ICollection<Data.PriceTiers>
I believe that Custom Type Converters is what you need.
Create Custom Type Converters.
public class ICollectionDataPriceTiersTypeConverter : ITypeConverter<DTOs.PriceTiers, ICollection<Data.PriceTiers>>
{
public ICollection<Data.PriceTiers> Convert(DTOs.PriceTiers src, ICollection<Data.PriceTiers> dest, ResolutionContext context)
{
if (src == null)
return default;
var singleDest = context.Mapper.Map<Data.PriceTiers>(src);
return new List<Data.PriceTiers>
{
singleDest
};
}
}
Add to mapping profile.
CreateMap<DTOs.PriceTiers, ICollection<Data.PriceTiers>>()
.ConvertUsing<ICollectionDataPriceTiersTypeConverter>();
Demo # .NET Fiddle
Solution 2: Map from ICollection<DTOs.PriceTiers> to ICollection<Data.PriceTiers>
If the PriceTiers in DTOs.Product supports multiple items and mapping with many to many (to ICollection<Data.ProductTiers>), then consider modifying the property as the ICollection<DTOs.PriceTiers> type.
namespace DTOs
{
public class Product
{
...
public ICollection<PriceTiers> PriceTiers { get; set; }
}
}

Did you added "CreateMapper()" method after your configurations?
Try something like that.
public class MappingProfile : Profile
{
public MappingProfile {
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
}
}
After that, on your container service, inject this dependency:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);

After some more research I found out that my mapping profile was not in the right order. These are the changes I made.
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
AllowNullCollections = true;
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
CreateMap<DTOs.Product, Data.Product>()
.ForMember(dto => dto.PriceTiers, opt => opt.MapFrom(x => x.PriceTiers));
}
}
Now it maps perfectly

Related

Strange Automapper behavior for derived object mapping

guys.
I have a strange behavior of Automapper when I try to map derived objects from one root.
For example:
public class User
{
public Guid Id { get; set; }
}
public class DerivedUser1 : User
{
public int SomeProp1 { get; set; }
}
public class DerivedUser2 : User
{
public int SomeProp2 { get; set; }
public class SubDerivedUser3 : DerivedUser2
{
public int SomeProp3 { get; set; }
}
I use CreateMap like this:
CreateMap<User, User>()
.ForMember(e => e.Id, f => f.Ignore())
.IncludeAllDerived()
.AfterMap((src, dest) =>
{
dest.Id = Guid.NewGuid();
});
Let see the code of mapping below:
var testUser1 = new DerivedUser1();
var testUser2 = new SubDerivedUser3();
mapper.Map(testUser2, testUser1, testUser2.GetType(), testUser1.GetType());
When I try to execute this code, I have an error:
"No coercion operator is defined between types 'DerivedUser1' and
'DerivedUser2'"
I thought that IncludeAllDerived() always check a whole derivation tree. Could someone explain to me the reason of this error?
Thank you.

Why I get exception when I try to use AutoMapper?

I use AutoMapper in my .NET CORE 2.2 project.
I get this exception:
Missing type map configuration or unsupported mapping.
Mapping types:
SaveFridgeTypeModel -> FridgeType
College.Refrigirator.Application.SaveFridgeTypeModel ->
College.Refrigirator.Domain.FridgeType
On This row:
var fridgeType = _mapper.Map<SaveFridgeTypeModel, FridgeType>(model);
Here is defenition of FridgeType class:
public class FridgeType : IEntity , IType
{
public FridgeType()
{
Fridges = new HashSet<Fridge>();
}
public int ID { get; set; }
//Description input should be restricted
public string Description { get; set; }
public string Text { get; set; }
public ICollection<Fridge> Fridges { get; private set; }
}
Here is defenition of SaveFridgeTypeModel class:
public class SaveFridgeTypeModel
{
public string Description { get; set; }
public string Text { get; set; }
}
I add this row:
services.AddAutoMapper(typeof(Startup));
To ConfigureServices function in Startup class.
UPDATE
I forgot to add mappin configuration to the post.
Here is mapping configs class:
public class ViewModelToEntityProfile : Profile
{
public ViewModelToEntityProfile()
{
CreateMap<SaveFridgeTypeModel, FridgeType>();
}
}
Any idea why I get the exception above?
You need to use the type from the assembly where your maps are when registering automapper with DI.
AddAutomapper(typeof(ViewModelToEntityProfile));
If you had multiple assemblies with maps - you could use another overload:
AddAutomapper(typeof(ViewModelToEntityProfile), typeof(SomeOtherTypeInOtherAssembly));
After creating mapping config class you need to add the AutoMapperConfiguration in the Startup.cs as shown below:
public void ConfigureServices(IServiceCollection services) {
// .... Ignore code before this
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new ViewModelToEntityProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}

AutoMapper - Map Derived Class To Dto

Im trying to map a Class which inherits from a base class to a dto.
public class LaunchConfiguration : Document
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
public class LaunchConfigurationDto
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
The point of the dto is to hide the fields of the base document when it gets returned to the user. This is my Map configuration
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration,LaunchConfigurationDto>();
}
};
The problem im having is that auto mapper complains about the base class properties which are not mapped . "Unmapped members were found." The properties are the ones on the base class. I have tried specifying this to be ignored in the profile to no avail . Can anyone specify the correct way to do this ?
My ConfigureServices Method incase anyone is wondering :
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = Configuration["ApiInformation:Name"], Version = Configuration["ApiInformation:Version"] });
c.DescribeAllEnumsAsStrings();
});
services.AddAutoMapper(mc =>
{
mc.AddProfile(new DtoProfile());
});
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
}
My Base Class :
public class Document : IDocument, IDocument<Guid>
{
public Document()
{
this.Id = Guid.NewGuid();
this.AddedAtUtc = DateTime.UtcNow;
}
/// <summary>The Id of the document</summary>
[BsonId]
public Guid Id { get; set; }
/// <summary>The datetime in UTC at which the document was added.</summary>
public DateTime AddedAtUtc { get; set; }
/// <summary>The version of the schema of the document</summary>
public int Version { get; set; }
}
My implementation where _mapper is my Injected mapper and _repo My Injected Repo. Exception Occurs on Map Method call
Task ILaunchConfigurationService<LaunchConfigurationDto >.InsertLaunchConfiguration(LaunchConfigurationDto model)
{
var mapped = _mapper.Map<LaunchConfiguration >(model);
return _repo.AddOneAsync(mapped);
}
Your problem should be solved by simply adding ReverseMap() to CreateMap call:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>().ReverseMap();
}
};
Automapper creates one way map by default. ReverseMap is just a sugar for creating reverse map in case there are no peculiar mappings in one way. You could also do it like this:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>();
CreateMap<LaunchConfigurationDto, LaunchConfiguration>();
}
};
You can read more about this in documentation
However I cannot guarantee you that you will not experience exceptions from database with your current implementation on commiting changes.

Automapper nests parent entity inside child entity

I am having an issue when mapping nested classes that reference each other. Entity framework populares the nested entities in a loop so I end up with the parent nested inside the nested child entity after mapping.
I made a sample program you can copy paste that shows the problem. In my actual program I am mapping collections so it would require to loop over entire collection to set all the nested object to null and that does not feel neat, I would rather adjust my mapping rules if possible.
Here is code that shows the issue:
using AutoMapper;
public class Job
{
public string Name { get; set; }
public PayPackage PayPackage { get; set; }
}
public class PayPackage
{
public string Name { get; set; }
public Job Job { get; set; }
}
public class JobViewModel
{
public string Name { get; set; }
public PayPackageViewModel PayPackage { get; set; }
}
public class PayPackageViewModel
{
public string Name { get; set; }
public JobViewModel Job { get; set; }
}
class Program
{
static void Main(string[] args)
{
var job = new Job
{
Name = "Job Name",
PayPackage = new PayPackage
{
Name = "Pay Package Name"
}
};
job.PayPackage.Job = job; //simulate how EF is populating entity
var config = new MapperConfiguration(c =>
{
c.CreateMap<Job, JobViewModel>();
c.CreateMap<JobViewModel, Job>();
c.CreateMap<PayPackage, PayPackageViewModel>();
c.CreateMap<PayPackageViewModel, PayPackage>();
});
var mapper = config.CreateMapper();
var jobVm = mapper.Map<JobViewModel>(job);
Assert.IsTrue(jobVm.PayPackage != null);
Assert.IsTrue(jobVm.PayPackage.Job == null); //how do I specify mapping so this passes?
}
}
What is the best way to avoid the parent appearing twice in the mapped result?
E.g.
c.CreateMap<Job, JobViewModel>()
.ForMember(dest => dest.PayPackage, opt => opt.Ignore());
The PayPackage property of the destination JobViewModel object will be ignored when the mapping occurs.

Fluent NHibernate PropertyNotFoundException for Auto Property

I'm trying to get Fluent NHibernate to map a collection for me. My class definitions are as follows:
public abstract class Team
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class ClientTeam : Team
{
public virtual IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Identifiers { get; set; }
}
My mappings:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Table("Team");
Id(x => x.Id).GeneratedBy.Assigned();
Map(t => t.TeamName);
}
}
public class ClientTeamMap : SubclassMap<ClientTeam>
{
public ClientTeamMap()
{
HasMany(t => t.Clients);
}
}
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Table("Client");
Id(c => c.Id);
Map(c => c.Name);
Map(c => c.Identifiers);
}
}
I've built a unit test that instantiates a team and then attempts to persist it (the test base has dependency configuration, etc. in it):
public class TeamMapTester : DataTestBase
{
[Test]
public void Should_persist_and_reload_team()
{
var team = new ClientTeamDetail
{
Id = Guid.NewGuid(),
TeamName = "Team Rocket",
Clients = new[]
{
new ClientDetail {ClientName = "Client1", ClientIdentifiers = "1,2,3"}
}
};
using (ISession session = GetSession())
{
session.SaveOrUpdate(team);
session.Flush();
}
AssertObjectWasPersisted(team);
}
}
When I run the test, I get this error:
SetUp : FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
Database was not configured through Database method.
----> NHibernate.MappingException: Could not compile the mapping document: (XmlDocument)
----> NHibernate.PropertyNotFoundException : Could not find field '_clients' in class 'ClientTeam'`
I've looked through the NHibernate documentation and done some google searching, but I can't find anything that appears to address this issue. The documentation for Fluent NHibernate's Referencing methods explicitly uses auto properties, so I'm sure that's not the issue.
Why might NHibernate think that _clients is the field it should map in this case?
And the reason turns out to be: Conventions.
The Fluent mappings were set up to try to enforce read-only collection properties, by requiring a backing field. The ICollectionConvention in question:
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
instance.Fetch.Join();
instance.Not.LazyLoad();
instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
}
}
which requires that collection backing fields be camelCased and start with an underscore.

Categories