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();
Related
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; }
}
I have a property in my destination entity with IgnoreMap attribute.
I want to disable only once. I use Automapper list to list mapping.
public class TestDto {
public string Name { get; set; }
public DateTime UpdateDate { get; set; }
}
public class Test {
public string Name { get; set; }
//Normally, I want to ignore this entities all mapping except one method.
[IgnoreMap]
public DateTime UpdateDate { get; set; }
}
class Program {
public void MapMethod(List<TestDto> sourceList)
{
var content = new MapperConfigurationExpression();
content.CreateMap<TestDto,Test>();
var config = new MapperConfiguration(content);
var mapper = config.CreateMapper();
//I do not want to ignore UpdateDate entity in here.
var destinationList = mapper.Map<List<Test>>(sourceList);
}
}
you can try this:
_mapper.Map<DestType>(result, options => options.AfterMap((s, d) => ((DestType) d).Code = null));
Full Example
void Main()
{
IConfigurationProvider conf = new MapperConfiguration(exp => exp.CreateMap<Src, Dest>());
IMapper mapper = new Mapper(conf);
var src = new Src(){
Id =1,
Name= "John Doe"
};
var result = mapper.Map<Dest>(src, options => options.AfterMap((s, d) => ((Dest) d).Name = null));
result.Dump();
var result2 = mapper.Map<List<Dest>>(srcList, options => options.AfterMap((s, d) => ((List<Dest>) d).ForEach(i => i.Name = null)));
result2.Dump();
}
public class Src
{
public int Id {get; set;}
public string Name {get; set;}
}
public class Dest
{
public int Id {get; set;}
public string Name {get; set;}
}
Alternatively
void ConfigureMap(IMappingOperationOptions<Src, Dest> opt)
{
opt.ConfigureMap()
.ForMember(dest => dest.Name, m => m.Ignore());
};
var result3 = mapper.Map<List<Dest>>(srcList, ConfigureMap());
result3.Dump();
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>();
});
});
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; }
}
Using Automapper, I want to map a property that is a List of type Employee using string.Join() to product a comma-delimited string of the names of an employee's rights. Here are the classes I'm using:
public class MappedEmployee
{
public string Name { get; set; }
public string RightNames { get; set; }
}
public class Employee
{
public string Name { get; set; }
public List<Right> Rights { get; set; }
}
public class Right
{
public string Name { get; set; }
}
And here is the code I have:
Mapper.CreateMap<Employee, MappedEmployee>()
.ForMember(d => d.RightNames, o => o.MapFrom(s => s.Rights.SelectMany(r => string.Join(", ", r.Name))));
var employee = new Employee
{
Name = "Joe Schmoe",
Rights = new List<Right>
{
new Right { Name = "Admin" },
new Right { Name = "User" },
}
};
var mappedEmployee = Mapper.Map<Employee, MappedEmployee>(employee);
However, this it's producing the folowing:
System.Linq.Enumerable+<SelectManyIterator>d__14`2[Employee.Right,System.Char]
What can I do do get a comma-delimited string of the Employee's rights?
Try using ResolveUsing instead and putting string.Join before the selection:
Mapper.CreateMap<Employee, MappedEmployee>()
.ForMember(d => d.RightNames, o => o.ResolveUsing(s => string.Join(", ",s.Rights.Select(r => r.Name))));