I set up a simple console application, and I'm trying to convert:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
To:
public class user_dto
{
public string ud_first_name { get; set; }
public string ud_lastname { get; set; }
}
and then convert them back. Here's my attempt:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(cfg =>
{
cfg.RecognizeDestinationPrefixes(new []{"ud_"});
cfg.RecognizePrefixes(new[] { "ud_" });
cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
cfg.CreateMap<User, user_dto>().ReverseMap();
});
}
}
static void Main(string[] args)
{
AutoMapperConfig.RegisterMappings();
var newUser = new User
{
FirstName = "John",
LastName = "Doe"
};
var newUser2 = new user_dto
{
ud_first_name = "Marry",
ud_lastname = "Jane"
};
var newUserDTO = AutoMapper.Mapper.Map<user_dto>(newUser);
Console.Write(newUserDTO.ud_first_name + " " + newUserDTO.ud_lastname);
var newUserViewModel = AutoMapper.Mapper.Map<User>(newUser2);
Console.Write(newUserViewModel.FirstName + " " + newUserViewModel.LastName);
Thread.Sleep(100);
}
This works fine for converting the last name, but not the first name. Per the docs the naming convention being setup as I have it should convert first_name -> FirstName, but it silently fails and only the lastname is converted.
Interestingly enough, if I remove the prefix lines in the config and also remove the leading ud_ from the user_dto and run the application, John Doe's whole name is converted, but only Marry's last name is converted. Are you not able to remove prefixis and use the naming conventions to convert with automapper? Is there supposed to be a different/better way to achieve what I'm trying?
I don't know if you are able to change the names of properties in the dto but if you can it seems to work fine in version 4.2.1 or AutoMapper if you make consistent the use of underscores.
public class user_dto
{
public string ud_firstname { get; set; }
public string ud_lastname { get; set; }
}
Output:
John DoeMary Jane
I would guess that AutoMapper is getting confused with the number of underscores, which is why it partially worked when you removed the ud_ part
UPDATE:
This feels wrong but it does work, so maybe it is a workaround in this situation. Add the following lines to the config to allow for custom mapping after the auto mapping has been done.
cfg.CreateMap<User, user_dto>().ReverseMap().AfterMap((src, dest) => dest.FirstName = src.ud_first_name);
cfg.CreateMap<user_dto, User>().ReverseMap().AfterMap((src, dest) => dest.ud_first_name = src.FirstName);
Full Config Class:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(cfg =>
{
cfg.RecognizeDestinationPrefixes(new []{"ud_"});
cfg.RecognizePrefixes(new[] { "ud_" });
cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
cfg.CreateMap<User, user_dto>().ReverseMap().AfterMap((src, dest) => dest.FirstName = src.ud_first_name);
cfg.CreateMap<user_dto, User>().ReverseMap().AfterMap((src, dest) => dest.ud_first_name = src.FirstName);
});
}
}
The following works as expected:
Mapper.Initialize(cfg =>
{
cfg.CreateProfile("UserViewModelToDto", prf =>
{
prf.RecognizeDestinationPrefixes(new[] { "ud" });
prf.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
prf.CreateMap<User, user_dto>();
});
cfg.CreateProfile("UserDtoToViewModel", prf =>
{
prf.RecognizePrefixes(new[] { "ud_" });
prf.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
prf.CreateMap<user_dto, User>();
});
});
Related
I am trying to create a MongoDB update document in my C# application. I used to MongoDB.Driver and was able to do for a simple class.
class MyTest
{
public string Name { get; set; }
public string Description { get; set; }
}
public static void Main(string[] args)
{
var v1 = Builders<MyTest>.Update
.Set(t => t.Name, "TestName")
.Set(t => t.Description, "TestDescription");
var output = v1.Render(BsonSerializer.LookupSerializer<MyTest>(), new BsonSerializerRegistry()).ToString();
}
This produced expected output
{ "$set" : { "Name" : "TestName", "Description" : "TestDescription" } }
However, if I have a nested structure like this:
class MyInnerClass
{
public string Id { get; set; }
}
class MyTest
{
public string Name { get; set; }
public string Description { get; set; }
public List<MyInnerClass> InnerClasses { get;} = new List<MyInnerClass>();
}
public static void Main(string[] args)
{
var v1 = Builders<MyTest>.Update
.Set(t => t.Name, "TestName")
.Set(t => t.Description, "TestDescription")
.Set(t => t.InnerClasses[0].Id, "TestId");
var output = v1.Render(BsonSerializer.LookupSerializer<MyTest>(), new BsonSerializerRegistry()).ToString();
}
I get the exception when doing Render:
System.InvalidOperationException: 'Unable to determine the
serialization information for t => t.InnerClasses.get_Item(0).Id.'
How to do this correctly?
Note that, I want the string representation of the update for my API call and not updating any real DB.
Try:
BsonClassMap.RegisterClassMap<MyTest>((b) =>
{
b.AutoMap();
b.MapField(f=>f.InnerClasses);
});
var v1 = Builders<MyTest>.Update
.Set(t => t.Name, "TestName")
.Set(t => t.Description, "TestDescription")
.Set(t => t.InnerClasses[0].Id, "TestId");
var output = v1.Render(BsonSerializer.LookupSerializer<MyTest>(), new BsonSerializerRegistry()).ToString();
another option, you can simply provide a raw query like:
var v1 = Builders<MyTest>.Update
.Set(t => t.Name, "TestName")
.Set(t => t.Description, "TestDescription")
.Set("InnerClasses.0._id", "TestId");
var registry = BsonSerializer.SerializerRegistry;
var serializer = registry.GetSerializer<MyTest>();
var output = v1.Render(serializer, registry).ToString();
In the current example I find it easy going HeaderPayload => Header since I just use the source.Data.Values directly.
What let's me construct the other way Header => HeaderPayload?
The current config is wrong and fails in the Map call.
[Test]
public void TestAutoMapper2()
{
// arrange
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Header, HeaderPayload>()
.ForMember(dest => dest.Data, map => map.MapFrom(src => src.Values))
;
});
var a = new Header { Values = new List<string> { "foo", "bar" } };
var mapper = new Mapper(config);
// act
var b = mapper.Map<Header>(a);
// assert
Assert.IsNotNull(b.Data);
Assert.IsNotEmpty(b.Data.Values);
Assert.AreEqual("foo", b.Data.Values.FirstOrDefault());
}
public class Data
{
public List<string> Values { get; set; }
}
public class Header
{
public List<string> Values { get; set; }
}
public class HeaderPayload
{
public Data Data { get; set; }
}
public class SourceExamModel
{
public int ExamId { get; set; }
public List<SectionModel> Sections { get; set; }
}
public class DesiationExamModel
{
public in ExamId {get;set;}
public System.Collections.Generic.IEnumerable<SectionModel> DestSections
{
get
{
}
set
{
}
}
What I tried:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<CrmMapper.SourceExamModel, CrmMapper.DestiationExamModel>()
.ForMember(v => v.Id, opts => opts.MapFrom(src => src.Id))
.ForMember(v => v.DestSections, opts => opts.MapFrom(src => src.SourceSections));
});
IMapper mapper = config.CreateMapper();
var source = new ExamModel();
var dest = mapper.Map<SourceExamModel, CrmMapper.DestiationExamModel>(source);
Can ayone help me how to map list of comple objects o los of complex objects
Assuming half of your example code is simple spelling mistakes, you pretty much have it working.
If the properties on the source and destination are named the same, you don't have to explicitly map them.
Your source in your example doesn't make sense, it needs the correct object and data.
Here's my attempt at a working example you can copy past into a console application.
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<SourceExamModel, DestinationExamModel>()
.ForMember(dest => dest.DestSections, c => c.MapFrom(src => src.Sections))
);
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var source = new SourceExamModel
{
ExamId = 1,
Sections = new List<SectionModel> { new SectionModel { SectionId = 1 }, new SectionModel { SectionId = 2 } }
};
var destination = mapper.Map<SourceExamModel, DestinationExamModel>(source);
}
}
public class SourceExamModel
{
public int ExamId { get; set; }
public List<SectionModel> Sections { get; set; }
}
public class DestinationExamModel
{
public int ExamId { get; set; }
public List<SectionModel> DestSections { get; set; }
}
public class SectionModel
{
public int SectionId { get; set; }
}
Not sure that I'm wording this the right way so hopefully the example is clear enough.
What I'm trying to do seems pretty basic to me so I'm assuming I'm missing something obvious.
For this example, the two ForMember mappings are trivial and get the job done. The question is for a more complex class, given any intermediate mappings are configured, how do you simply map the property of one object to the entire destination?
I searched for a while now and the closest I came to finding an answer is here but the ConvertUsing syntax doesn't work for me (I'm using Automapper 4.2.1)
Here are the example classes:
public class UserRoleDto
{
public string Name { get; set; }
public string Description { get; set; }
}
public class DbRole
{
public Guid RoleId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class DbUserRole
{
public Guid UserId { get; set; }
public DbRole Role { get; set; }
}
And here's my test case with the Automapper config setup (testing in LINQPad, that's what the Dump() is for at the end of the last line)
var dbRole = new DbRole { RoleId = Guid.NewGuid(), Name = "Role Name", Description = "Role Description" };
var dbUserRole = new DbUserRole { UserId = Guid.NewGuid(), Role = dbRole };
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<DbRole, UserRoleDto>();
/* Works but verbose for a class with more than a few props */
cfg.CreateMap<DbUserRole, UserRoleDto>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Role.Name))
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Role.Description))
;
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var userRoleDto = mapper.Map<UserRoleDto>(dbUserRole).Dump();
How about passing in the sub-object to be mapped? E.g.
cfg.CreateMap<DbRole, UserRoleDto>();
Then, instead of mapping dbUserRole, you would map dbUserRole.Role.
var userRoleDto = mapper.Map<UserRoleDto>(dbUserRole.Role);
Here is another similar example using the following classes:
public class Person
{
public int person_id;
public int age;
public string name;
}
public class Address
{
public int address_id;
public string line1;
public string line2;
public string city;
public string state;
public string country;
public string zip;
}
public class PersonWithAddress
{
public int person_id;
public int age;
public string name;
public InnerAddress address;
}
public class InnerAddress
{
public string city;
public string state;
public string country;
}
With the following test case:
var person = new Person { person_id = 100, age = 30, name = "Fred Flintstone" };
var address = new Address { address_id = 500, line1 = "123 Main St", line2 = "Suite 3", city = "Bedrock", state = "XY", country = "GBR", zip="90210" };
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Person, PersonWithAddress>();
cfg.CreateMap<Address, InnerAddress>();
});
var mapper = config.CreateMapper();
var person_with_address = mapper.Map<Person, PersonWithAddress>(person);
person_with_address.address = new InnerAddress();
mapper.Map<Address, InnerAddress>(address, person_with_address.address);
Regards,
Ross
I'm totally confused about a value not being stored. There's an instance of a class that has a member defined like this.
public class MyHolder
{
public List<MyPart> Parts { get; set; }
}
public class MyPart
{
public bool Taken { get; set; }
public int Id { get; set; }
}
I try to probe that class for IDs to accepted elements and it doesn't work because the updates I'm making don't seem to get through. So I've created the following, extremely simple probe.
List<int> before = myHolder.Where(e => e.Taken).Select(f => f.Id).ToList();
myHolder.First(p => p.Id == 7).Taken = false;
List<int> before = myHolder.Where(e => e.Taken).Select(f => f.Id).ToList();
To my great surprise, the number of before and after stays the same! I've verified that for all the IDs and I've made sure that e.g. 7 is true from the start. I even tried initiating it with false and then setting it to true. There's no other logic going on, as far I can see. I know for sure that it's me doing something wrong but I'm not sure what it is. And it's kind of hard to search for it because this weird behavior is very generic.
It's not like we create a copy of myHolder and put the updated value in it. And if it's so, how can I obtain and write to the real thing?
I'm hoping that someone sees something obvious. Or at least points me in a good direction to search more.
Did you mean something like this:
public class MyHolder
{
public List<MyPart> Parts { get; set; }
}
public class MyPart
{
public int Id { get; set; }
public bool Taken { get; set; }
public string Name { get; set; }
}
In this code update works as expected
var myHolder = new MyHolder {
Parts = new List<MyPart> {
new MyPart { Id = 7, Taken = true, Name = "Test" },
new MyPart { Id = 8, Taken = false, Name = "Test 1" }
}
};
var before = myHolder.Parts.Where(e => e.Taken).Select(f => f.Id).ToList();
Console.WriteLine(before.Count());
myHolder.Parts.First(p => p.Id == 7).Taken = false;
var after = myHolder.Parts.Where(e => e.Taken).Select(f => f.Id).ToList();
Console.WriteLine(after.Count());
See working fiddle
This worked for me -
public class MyHolder
{
public List<MyPart> Parts { get; set; }
}
public class MyPart
{
public int Id { get; set; }
public bool Taken { get; set; }
public string Name { get; set; }
}
var holder = new MyHolder() { Parts = new List<MyPart>() { new MyPart() { Id = 7, Name = "R", Taken = true }, new MyPart() { Id = 8, Name = "S", Taken = true }, new MyPart() { Id = 9, Name = "T", Taken = true } } };
List<int> before = holder.Parts.Where(m => m.Taken).Select(f => f.Id).ToList();
holder.Parts.First(p => p.Id == 7).Taken = false;
List<int> after = holder.Parts.Where(m => m.Taken).Select(f => f.Id).ToList();