I have configured AutoMapper 'AutoMapper.Extensions.Microsoft.DependencyInjection Version 7.0'in my Web API application as following; I get the Missing Type Map configuration Mapping error.
Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<SupplierContext>(options =>
options.UseInMemoryDatabase(databaseName: "MyDatabase"));
services.AddTransient<IAppService, AppService>();
var profiles = from type in typeof(Startup).Assembly.GetTypes()
where typeof(Profile).IsAssignableFrom(type)
select (Profile)Activator.CreateInstance(type);
var mapConfig = new MapperConfiguration(cfg =>
{
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
});
var mapper = mapConfig.CreateMapper();
services.AddSingleton(mapper);
}
I have created a profile in main project API under the folder
profile
public UserProfile()
{
CreateMap<User, UserDataView>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(dataModel => dataModel.Id))
.ForMember(dest => dest.Title, opt => opt.MapFrom(dataModel => dataModel.Title))
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(dataModel => dataModel.FirstName))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(dataModel => dataModel.LastName))
.ForMember(dest => dest.ActivationDate, opt => opt.MapFrom(dataModel => dataModel.ActivationDate));
//CreateMap<User, UserDataView>()
// .ReverseMap();
}
User class
public class User
{
public Guid Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime ActivationDate { get; set; }
}
When I try to map model data to Dto, I get missing type map configuration error
Error
Missing type map configuration or unsupported mapping.
Mapping types:
Task`1 -> List`1
System.Threading.Tasks.Task`1[[System.Collections.Generic.List`1[[MyApp.Model.User.User, MyApp.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea70000]] -> System.Collections.Generic.List`1[[MyApp.Model.DTOs.UserDataView, MyApp.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Auto Mapping
var UserDataModel = (from user in _context.Users
join email in _context.Emails on user.Id equals email.userId into se
join phone in _context.Phones on user.Id equals phone.userId into sp
select new User
{
Id = user.Id,
Title = user.Title,
FirstName = user.FirstName,
LastName = user.LastName,
ActivationDate = user.ActivationDate,
}).ToListAsync();
var dataResult = _mapper.Map<List<UserDataView>>(UserDataModel);
I have found my mistake. The UserDataModel is of Type Task<List> where my profile class is expecting different.
I have changed my LINQ script as following and it did work
var UserDataModel = (from user in _context.Users
join email in _context.Emails on user.Id equals email.userId into se
join phone in _context.Phones on user.Id equals phone.userId into sp
select new User
{
Id = user.Id,
Title = user.Title,
FirstName = user.FirstName,
LastName = user.LastName,
ActivationDate = user.ActivationDate,
});
var ddf = _mapper.Map<List<SupplierDataView>>(supplierDataModel);
Related
I have a PITA legacy DB model. Here is the relevant portion
class SalesOrder
{
public Recipient OrderBy {get;set;}
public Recipient BillTo {get;set;}
public List<SalesOrderLine> Lines {get;set;}
}
class SalesOrderLine
{
public Recipient ShipTo {get;set;}
public Address ShipToAddress {get;set;}
}
class Recipient
{
public Address DefaultAddress {get;set;}
}
Now comes the fun part.
class RecipientDTO {
public string Name {get;set;}
public string Address1 {get;set;}
public string Address2 {get;set;}
...
}
I have the OrderDTO that needs to be like this
new OrderDTO {
OrderBy = new RecipientDTO {
Name = OrderBy.Name,
Address1 = OrderBy.DefaultAddress.Addr1,
....
}
BillTo = new RecipientDTO {
Name = BillTo.Name,
Address1 = BilLTo.DefaultAddress.Addr1,
....
}
Lines = Lines.Select (l => new SalesOrderLineDTO {
ShipTo = new RecipientDTO {
Name = ShipTo.Name,
Address1 = l.ShipToAddress.Addr1, //NOTE. THIS IS NOT USING DEFAULT ADDRESS
....
}
} )
}
How do I write the mapping configuration in Auto Mapper for this to make sure the projection emits the chosen columns. If I use CustomResolver, the projection is not emitting the sql and it goes to the DB every time i access the address. Mucho sad!
To anyone who is interested , i have a backreference to Recipient from Address. SO I was able to do this following
m.CreateMap<SalesOrder, OrderDTO>()
.ForMember(d => d.OrderBy, conf => conf.MapFrom(o => o.OrderBy.DefaultAddress))
.ForMember(d => d.BillTo, conf => conf.MapFrom(o => o.BillTo.DefaultAddress));
m.CreateMap<Address, RecipientDTO>()
.ForMember(r => r.CustomerRecipientId, conf => conf.MapFrom(ra => ra.Recipient.CustRecipId))
.ForMember(r => r.Address1, conf => conf.MapFrom(ra => ra.Addr1))
.ForMember(r => r.Address2, conf => conf.MapFrom(ra => ra.Addr2))
....
m.CreateMap<SalesOrderLine, OrderLineDTO>()
.ForMember(l => l.OrderQuantity, conf => conf.MapFrom(l => l.OrderQty ?? 0))
.ForMember(l => l.ShipTo, conf => conf.MapFrom(z => z.ShipToAddress));
Is it possible to map one object to several objects using automapper? I know that it is possible to do it the other way around as shown here.
One way to do it:
Mapper.CreateMap<MAINSource, MAINDest>()
.ForMember(dest => dest.Inner1, expression => expression.ResolveUsing(source1 => source1.Inner1))
.ForMember(dest => dest.Inner2, expression => expression.ResolveUsing(source1 => source1.Inner2));
Mapper.CreateMap<Source1, Dest1>()
.ForMember(dest => dest.NumValue, expression => expression.ResolveUsing(source1 => source1.NumValue));
Mapper.CreateMap<Source1, Dest2>()
.ForMember(dest => dest.StringValue, expression => expression.ResolveUsing(source1 => source1.StringValue));
Full example:
public class Source1
{
public int NumValue { get; set; }
public string StringValue { get; set; }
}
public class MAINSource
{
public Source1 Inner1 { get; set; }
public Source1 Inner2 { get; set; }
}
public class Dest1
{
public int NumValue { get; set; }
}
public class Dest2
{
public string StringValue { get; set; }
}
public class MAINDest
{
public Dest1 Inner1 { get; set; }
public Dest2 Inner2 { get; set; }
}
Mapper.CreateMap<MAINSource, MAINDest>()
.ForMember(dest => dest.Inner1, expression => expression.ResolveUsing(source1 => source1.Inner1))
.ForMember(dest => dest.Inner2, expression => expression.ResolveUsing(source1 => source1.Inner2));
Mapper.CreateMap<Source1, Dest1>()
.ForMember(dest => dest.NumValue, expression => expression.ResolveUsing(source1 => source1.NumValue));
Mapper.CreateMap<Source1, Dest2>()
.ForMember(dest => dest.StringValue, expression => expression.ResolveUsing(source1 => source1.StringValue));
var innerSource = new Source1 {NumValue = 1, StringValue = "supervalue"};
var mainSource = new MAINSource
{
Inner1 = innerSource,
Inner2 = innerSource
};
var destination = Mapper.Map<MAINSource, MAINDest>(mainSource);
destination.Inner1.NumValue.ShouldEqual(1);
destination.Inner2.StringValue.ShouldEqual("supervalue");
I need to map to properties that are both collections of objects of the same type.
For example:
Destination:
public class MyNewObject
{
Collection TypeCollection1 { get; set; }
Collection TypeCollection2 { get; set; }
}
public class MyType
{
string Field1 { get; set; }
string Field2 { get; set; }
}
Source:
public class MyLegacyObject
{
Collection LegacyCollection1 { get; set; }
Collection LegacyCollection2 { get; set; }
}
public class MyLegacyType
{
string OldField1 { get; set; }
string OldField2 { get; set; }
}
For non-collections, I'm used to doing something like this:
Mapper.CreateMap()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(
src => new Collection
{
// is there some kind of ForEach thing I can do???
Field1 = src.OldField1,
Field2 = src.OldField2
??? // this obviously doesn't work because these are the properties on MyType, not the collection
}));
Mapper.CreateMap<MyLegacyType, MyType>()
.ForMember(dest => dest.Field1, opt => opt.MapFrom(src => src.OldField1))
.ForMember(dest => dest.Field2, opt => opt.MapFrom(src => src.OldField2));
Mapper.CreateMap<MyLegacyObject, MyNewObject>()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(src => src.LegacyCollection1))
.ForMember(dest => dest.TypeCollection2, opt => opt.MapFrom(src => src.LegacyCollection2))
I think I solved my own issue. This should do it.
I have a Domain model that countains the following properties :
public class Profile
{
public DateTime birthDate { get; set; }
public List<Language> languages { get; set; }
}
Where Language represent an enumeration defined here :
public enum Language
{
English,
French,
Spannish
}
i would like to automatically populate the two following properties based on the languages values stored inside my domain model :
public Dictionary <int, String> languages_list { get; set; }
public List<string> languages_known { get; set; }
the question is : can it be done using automapper , if so how should i proceed ?
OK, using the detail you've provided this is what I've come up with.
Classes
public class SOProfile
{
public DateTime birthDate { get; set; }
public List<Language> languages { get; set; }
}
public class WhatAmI
{
public Dictionary<int, String> languages_list { get; set; }
public List<string> languages_known { get; set; }
}
Note that Profile was renamed to SOProfile to avoid a clash with AutoMapper
AutoMapper Configuration
public class MyProfile : Profile
{
public override string ProfileName
{
get
{
return "MyProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<SOProfile, WhatAmI>()
.ForMember(dest => dest.languages_list,
opt => opt.MapFrom(
src => src.languages
.Select((x,i) => new { Item = x, Index = i})
.ToDictionary(x => x.Index,
x => x.Item.ToString())))
.ForMember(dest => dest.languages_known,
opt => opt.MapFrom(
src => src.languages
.Select(x => x.ToString()).ToList()));
}
}
Unit Tests
[TestFixture]
public class MappingTests
{
[Test]
public void AutoMapper_Configuration_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
}
[Test]
public void AutoMapper_Mapping_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
var profile = new SOProfile
{
birthDate = new DateTime(2012, 01, 01),
languages = new List<Language>
{
Language.English,
Language.French,
Language.English,
Language.French
}
};
var rc = Mapper.Map<SOProfile, WhatAmI>(profile);
Assert.That(rc, Is.Not.Null);
Assert.That(rc.languages_known, Is.Not.Null);
Assert.That(rc.languages_known.Count, Is.EqualTo(4));
Assert.That(rc.languages_known.Count(x => x == "English"),
Is.EqualTo(2));
Assert.That(rc.languages_known.Count(x => x == "French"),
Is.EqualTo(2));
Assert.That(rc.languages_known.Count(x => x == "Spanish"),
Is.EqualTo(0));
Assert.That(rc.languages_list, Is.Not.Null);
Assert.That(rc.languages_list.Count, Is.EqualTo(4));
Assert.That(rc.languages_list.First(x => x.Key == 0).Value,
Is.EqualTo("English"));
Assert.That(rc.languages_list.First(x => x.Key == 1).Value,
Is.EqualTo("French"));
Assert.That(rc.languages_list.First(x => x.Key == 2).Value,
Is.EqualTo("English"));
Assert.That(rc.languages_list.First(x => x.Key == 3).Value,
Is.EqualTo("French"));
}
}
My current code is working:
map.ForMember(x => x.Address, m => m.ResolveUsing(l => {
var engine = new MappingEngine((IConfigurationProvider)cfg);
var adress = engine.Map<AddressDto>(l.ContactInfo);
engine.Map(l.Address, adress);
return adress;
}));
but I thought there might be another way, something like:
map.ForMember(x => x.Address, m => m.MapFrom(x => x.ContactInfo));
map.ForMember(x => x.Address, m => m.MapFrom(x => x.Address));
But the last ForMember seems to override the existing map.
I'm trying to combine Address and ContactInfo properties into a single object on ListingDto.Address.
void Main()
{
var map = Mapper.CreateMap<Listing, ListingDto>();
var cfg = Mapper.Configuration;
map.ForMember(x => x.Address, m => m.ResolveUsing(l => {
var engine = new MappingEngine((IConfigurationProvider)cfg);
var adress = engine.Map<AddressDto>(l.ContactInfo);
engine.Map(l.Address, adress);
return adress;
}));
Mapper.CreateMap<Address, AddressDto>()
.ForMember(x => x.Latitude, x => x.Ignore());
Mapper.CreateMap<ContactInfo, AddressDto>()
.ForMember(x => x.Street, x=> x.Ignore());
Mapper.Map<ListingDto>(new Listing{
Name="Foo",
Address = new Address{Street = "Street"},
ContactInfo = new ContactInfo{ Latitude = "latitude"}}).Dump();
}
// Define other methods and classes here
public class Listing{
public string Name { get; set; }
public Address Address { get; set; }
public ContactInfo ContactInfo {get;set;}
}
public class ContactInfo{
public string Latitude {get;set;}
}
public class Address{
public string Street {get;set;}
}
public class AddressDto{
public string Latitude {get;set;}
public string Street {get;set;}
}
public class ListingDto{
public string Name { get; set; }
public AddressDto Address {get;set;}
}
.Dump() <-- is from linqpad to output
Something like this should work:
.ForMember(x => x.Address, o => o.MapFrom(
s => new AddressDto {
Latitude = s.ContactInfo.Latitude,
Street = s.Address.Street }));