I would like to ignore certain properties when mapping deep (ie levels > 1) object models.
The following test works fine:
class Foo
{
public string Text { get; set; }
}
class Bar
{
public string Text { get; set; }
}
Mapper.CreateMap<Foo, Bar>()
.ForMember(x => x.Text, opts => opts.Ignore());
var foo = new Foo { Text = "foo" };
var bar = new Bar { Text = "bar" };
Mapper.Map(foo, bar);
Assert.AreEqual("bar", bar.Text);
However when I try to do the same mapping but have Foo and Bar as properties on a parent class the following test fails:
class ParentFoo
{
public Foo Child { get; set; }
}
class ParentBar
{
public Bar Child { get; set; }
}
Mapper.CreateMap<ParentFoo, ParentBar>();
Mapper.CreateMap<Foo, Bar>()
.ForMember(x => x.Text, opts => opts.Ignore());
var parentFoo = new ParentFoo
{
Child = new Foo { Text = "foo" }
};
var parentBar = new ParentBar
{
Child = new Bar { Text = "bar" }
};
Mapper.Map(parentFoo, parentBar);
Assert.AreEqual("bar", parentBar.Child.Text);
Instead of ignoring the text of the Child class (ie left it as "bar") automapper sets the value to null. What am I doing wrong with my mapping configuration?
There are two ways Automapper can perform the mapping. The first way is to simply give Automapper your source object and it will create a new destination object and populate everything for you. This is the way most apps use Automapper. However, the second way is to give it both a source and an existing destination and Automapper will update the existing destination with your mappings.
In the first example, you're giving it an existing destination value so Automapper will use that. In the second example, Automapper is going to do the mapping for ParentFoo and ParentBar, but when it gets to the Child, it's going to create a new Child and then do the mapping (this is the default behavior). This results in the Text property being null. If you want to use the existing Child object, you'll need to configure Automapper to do that with UseDestinationValue:
Mapper.CreateMap<ParentFoo, ParentBar>()
.ForMember(b => b.Child, o => o.UseDestinationValue());
This makes your test pass (as long as you get rid of the first space when setting the parentBar.Child.Text!).
Related
Is there any way to get AutoMapper.Collection to skip the mapping of a collection if the source collection property is null?
In my case, the client may want to signal to the API that there is no need to update a given collection. A logical way do this would be to set the collection property to null in the dto sent from the client.
Basically:
If the source collection property is null: Do not touch the destination collection at all but leave it as-is. Do not clear it
If the source collection is not null: Do the collection mapping. If the source collection is empty this means clearing the destination collection
Is there any way to achieve this in AutoMapper.Collection? What I am looking for is this:
// Sample Classes
public class Entity
{
public ICollection<EntityChild> Children { get; set; }
}
public class EntityChild
{
public int Id { get; set; }
public string Value { get; set; }
}
public class Dto
{
public ICollection<DtoChild> Children { get; set; }
}
public class DtoChild
{
public int Id { get; set; }
public string Value { get; set; }
}
// AutoMapper setup including equality for children
CreateMap<Dto, Entity>();
CreateMap<DtoChild, EntityChild>()
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ReverseMap();
// Sample 1, null source collection
var entity = new Entity
{
Children = new List<EntityChild>
{
new() { Id = 1, Value = "Value 1" },
new() { Id = 2, Value = "Value 2" }
}
};
var dtoSkipChildren = new Dto
{
Children = null
};
// Since the source Children property is null, do not update the destination collection
mapper.Map(dtoSkipChildren, entity);
// Sample 2, empty source collection
entity = new Entity
{
Children = new List<EntityChild>
{
new() { Id = 1, Value = "Value 1" },
new() { Id = 2, Value = "Value 2" }
}
};
var dtoClearChildren = new Dto
{
Children = new List<DtoChild>()
};
// Now the source children is not null (but empty) so the destination collection should
// be updated (in this case cleared since the source collection is empty)
mapper.Map(dtoClearChildren, entity);
AutoMapper.Collection treats the null source Children property the same as the source Children property containing an empty collection. In both cases the destination Children collection is cleared.
I have tried to tell AutoMapper to skip the source.Children property if null:
CreateMap<Dto, Entity>()
.ForMember(dst => dst.Children, opt => opt.Condition(src => null != src.Children));
This does not change things. Also in this case, the source collection property is null when AutoMapper.Collection sets to work and the destination collection is cleared.
This is not a real solution either:
CreateMap<Dto, Entity>()
.ForMember(
src => src.Children,
opt => opt.MapFrom((src, dst, _, ctx) => src.Children ?? ctx.Mapper.Map<ICollection<DtoChild>>(dst.Children)));
This means reverse mapping the destination collection to the (null) source collection, so it can be mapped back. An ugly hack:
It's crossing the river twice to end up where you started
Wasted effort which is silly on large collections
Risky as the reverse map for other reasons may not be 100% thus introducing pretty hidden bugs
Does anyone have an advice on how to achieve this - or why my use case is not a good idea?
I had the same problem and I figured it out this way
CreateMap<Dto, Entity>()
.ForMember(
dest=> dest.Children,
opt => {
opt.PreCondition(src =>src.Children!= null);
opt.MapFrom(src =>src.Children);
});
Then in the place, you want to use mapping write code like this:
entity = mapper.Map<Dto, Entity>(dto , entity);
opt.PreCondition(src =>src.Children!= null) basically says to AutoMapper to proceed to map if the source field is not empty else do not map.
to read more look at this https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions
I'm using Dapper Extensions and have defined my own custom mapper to deal with entities with composite keys.
public class MyClassMapper<T> : ClassMapper<T> where T : class
{
public MyClassMapper()
{
// Manage unmappable attributes
IList<PropertyInfo> toIgnore = typeof(T).GetProperties().Where(x => !x.CanWrite).ToList();
foreach (PropertyInfo propertyInfo in toIgnore.ToList())
{
Map(propertyInfo).Ignore();
}
// Manage keys
IList<PropertyInfo> propsWithId = typeof(T).GetProperties().Where(x => x.Name.EndsWith("Id") || x.Name.EndsWith("ID")).ToList();
PropertyInfo primaryKey = propsWithId.FirstOrDefault(x => string.Equals(x.Name, $"{nameof(T)}Id", StringComparison.CurrentCultureIgnoreCase));
if (primaryKey != null && primaryKey.PropertyType == typeof(int))
{
Map(primaryKey).Key(KeyType.Identity);
}
else if (propsWithId.Any())
{
foreach (PropertyInfo prop in propsWithId)
{
Map(prop).Key(KeyType.Assigned);
}
}
AutoMap();
}
}
I also have this test case to test my mapper:
[Test]
public void TestMyAutoMapper()
{
DapperExtensions.DapperExtensions.DefaultMapper = typeof(MyClassMapper<>);
MySubscribtionEntityWithCompositeKey entity = new MySubscribtionEntityWithCompositeKey
{
SubscriptionID = 145,
CustomerPackageID = 32
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
var result = connection.Insert(entity);
var key1 = result.SubscriptionID;
var key2 = result.CustomerPackageID;
}
}
Note that I set the default mapper in the test case.
The insert fails and I notive that my customer mapper is never called. I have no documentation on the github page on the topic, so I'm not sure if there's anything else I need to do to make dapper extensions use my mapper.
Thanks in advance!
Looking at your question, you are attempting to write your own defalut class mapper derived from the existing one. I never used this approach; so I do not know why it is not working or whether it should work.
I explicitly map the classes as below:
public class Customer
{
public int CustomerID { get; set; }
public string Name { get; set; }
}
public sealed class CustomerMapper : ClassMapper<Customer>
{
public CustomerMapper()
{
Schema("dbo");
Table("Customer");
Map(x => x.CustomerID).Key(KeyType.Identity);
AutoMap();
}
}
The AutoMap() will map rest of the properties based on conventions. Please refer to these two resources for more information about mapping.
Then I call SetMappingAssemblies at the startup of the project as below:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { Assembly.GetExecutingAssembly() });
The GetExecutingAssembly() is used in above code because mapping classes (CustomerMapper and other) are in same assembly which is executing. If those classes are placed in other assembly, provide that assembly instead.
And that's it, it works.
To set the dialect, I call following line just below the SetMappingAssemblies:
DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();
Use your preferred dialect instead of SqlServerDialect.
Apparently, the solution mentioned here may help you achieve what you are actually trying to. But, I cannot be sure, as I said above, I never used it.
How do I configure the mapper so that this works?
(i.e. the properties from the dynamic object should map to the properties of the class definition with the same letters - ignoring case)
public class Foo {
public int Bar { get; set; }
public int Baz { get; set; }
}
dynamic fooDyn = new MyDynamicObject();
fooDyn.baR = 5;
fooDyn.bAz = 6;
Mapper.Initialize(cfg => {});
Foo result = Mapper.Map<Foo>(fooDyn);
result.Bar.ShouldEqual(5);
result.Baz.ShouldEqual(6);
If your dynamic object implements IDictionary<string,object> (e.g. ExpandoObject) then the following should work. There must be some easier way to do this as anonymous objects are mapped just fine even if the case is different.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<IDictionary<string, object>, Foo>()
.ConstructUsing(d =>
{
Foo foo = new Foo();
foreach (System.Reflection.PropertyInfo prop in typeof(Foo).GetProperties())
{
foreach (KeyValuePair<string, object> entry in d)
{
if (entry.Key.ToLowerInvariant() == prop.Name.ToLowerInvariant())
{
prop.SetValue(foo, entry.Value);
break;
}
}
}
return foo;
});
});
AutoMapper allows you to configure explicit member mapping on the map configuration in this style:
var config = new MapperConfiguration(cfg =>
{
var dynamicMap = cfg.CreateMap<IDictionary<string, object>, SomethingDTO>();
dynamicMap.ForAllMembers((expression) => expression.MapFrom(source =>
source.ContainsKey(expression.DestinationMember.Name.Substring(0, 1).ToLower()
+ expression.DestinationMember.Name.Substring(1))
? source[expression.DestinationMember.Name.Substring(0, 1).ToLower()
+ expression.DestinationMember.Name.Substring(1)] : null
));
});
For mapping a dynamic/expando object that is camel case to an type with pascal case members you could use ForAllMembers on the the explicit map configuration. Possible use case: json payloads to DTO.
In comparison to the other answer (which also works) this approach allows you to continue to benefit and use all the other map features and configuration.
I have 2 classes, Class1 should be mapped to Class2. I do mapping with AutoMapper. I'd like to test my configuration of the mapper and for this purposes I'm using AutoFixture. Source class Class1 has property of type IList<>, destination class Class2 has a similar property but of type IEnumerable<>. To simplify test preparation I'm using AutoFixture (with AutoMoqCustomization) to initialize both source and destination objects. But after initializing property of type IEnumerable<> with AutoFixture, AutoMapper can't map the property.
Error text:
Error mapping types.
Mapping types: Class1 -> Class2 ConsoleApplication1.Class1 ->
ConsoleApplication1.Class2
Type Map configuration: Class1 -> Class2 ConsoleApplication1.Class1 ->
ConsoleApplication1.Class2
Property: Items
Could anybody help me to configure either AutoMapper or AutoFixture to make the mapping work? As a workaround I can assign null to the destination property, but I do not want to do this in the each test.
Simplified example of code:
public class AutoMapperTests
{
public static void TestCollectionsProperty()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemClass1, ItemClass2>();
cfg.CreateMap<Class1, Class2>();
});
var src = new Class1();
src.Items = new List<ItemClass1>()
{
new ItemClass1() { Text = "111" },
new ItemClass1() { Text = "222" }
};
var fixture = new Fixture();
var dst = fixture.Create<Class2>();
Mapper.Map(src, dst); //Error at this line of code
}
}
public class Class1
{
public IList<ItemClass1> Items { get; set; }
}
public class Class2
{
public IEnumerable<ItemClass2> Items { get; set; }
}
public class ItemClass1
{
public string Text { get; set; }
}
public class ItemClass2
{
public string Text { get; set; }
}
It's not really an AutoFixture issue per se. You can reproduce it without AutoFixture by instead creating dst like this:
var dst = new Class2();
dst.Items = Enumerable.Range(0, 1).Select(_ => new ItemClass2());
This will produce a similar error message:
Unable to cast object of type 'WhereSelectEnumerableIterator2[System.Int32,Ploeh.StackOverflow.Q45437098.ItemClass2]' to type 'System.Collections.Generic.IList1[Ploeh.StackOverflow.Q45437098.ItemClass2]'
That ought to be fairly self-explanatory: WhereSelectEnumerableIterator<int, ItemClass2> doesn't implement IList<ItemClass2>. AutoMapper attempts to make that cast, and fails.
The simplest fix is probably to avoid populating dst:
var dst = new Class2();
If you must use AutoFixture for this, you can do it like this:
var dst = fixture.Build<Class2>().OmitAutoProperties().Create();
Unless the Class2 constructor does something complex, however, I don't see the point of using AutoFixture in that scenario.
If, on the other hand, you do need dst to be populated, you just need to ensure that dst.Items is convertible to IList<ItemClass2>. One way to do that would be like this:
var dst = fixture.Create<Class2>();
dst.Items = dst.Items.ToList();
You could create a Customization to make sure that this happens automatically, but if you need help with that, please ask a new question (if you don't find one that already answers that question).
Here is a working example for your problem. As #Mark Seemann already told, Mapper.CreateMap has been deprecated, so this example is using the new structure.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemClass1, ItemClass2>();
cfg.CreateMap<Class1, Class2>();
});
var src = new Class1();
src.Items = new List<ItemClass1>()
{
new ItemClass1() { Text = "111" },
new ItemClass1() { Text = "222" }
};
var dest = Mapper.Map<Class1, Class2>(src);
AM requires IList because you're mapping to an existing list and that works by calling IList.Add.
I have few classes and they have multiple list items like below:
public class Request1
{
public List<AdditionalApplicantData> AdditionalApplicantData { get; set;}
public List<ApplicantData> ApplicantData { get; set; }
}
public class Request2
{
public List<ApplicantDetails> ApplicantData { get; set; }
}
I want to map Request1 to Request2 but List of ApplicantData has to be mapped from multiple sources like List of ApplicantData & List of AdditionalApplicantData but not sure how to achieve it can someone please help me here?
You can use function below with createMap() function. Source: https://github.com/AutoMapper/AutoMapper/wiki/Before-and-after-map-actions
.AfterMap((src, dest) => {
dest.ApplicantData = /*your logic here*/
});
And you should mark ApplicantData as don't map because you have a variable named ApplicantData at the source class. You should implement the logic yourself.
EDIT:
When you are initializing mapper, you create map for each object. So for your case it would be like:
Mapper.Initialize(cfg => {
cfg.CreateMap<Request1, Request2>()
.ForMember(x => x.ApplicantData, opt => opt.Ignore()) //You want to implement your logic so ignore mapping
.AfterMap((src, dest) =>
{
dest.ApplicantData = /*implement your logic here*/
});
});
public class ApplicantDetailsResolver : IValueResolver<Request1, Request2, List<ApplicantDetails>>
{
public List<ApplicantDetails> Resolve(Request1 source, Request2 destination,List<ApplicantDetails> destMember, ResolutionContext context)
{
destination.ApplicantDetails = context.Mapper.Map<List<ApplicantDetails>>(source.ApplicantData);
for (int i = 0; i < destination.ApplicantDetails.Count(); i++)
{
context.Mapper.Map(source.AdditionalApplicantData.ElementAt(i), destination.ApplicantDetails.ElementAt(i));
}
return destination.ApplicantDetails;
}
}
I have written above custom value resolver for mapping list from multiple sources and its working fine but problem, is it can't match properties which are differently named, is there way I can handle this scenario as well?