I'm trying to use a generic mapper for mapping two objects. So I have setup Automapper in this way which comes from the documentation:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Source<>), typeof(Destination<>));
});
mapper = config.CreateMapper();
Now everything works well in the case if the source and destination have the same properties and if I'm going from more properties on the source side to less properties on the destination. However if the source has less properties than the destination than I get an error:
Value ---> AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
My question is there a way I could ignore these properties even though I won't know what they are at compile time?
Source:
public class Source<T>
{
public T Value { get; set; }
}
public class Destination<T>
{
public T Value { get; set; }
}
public class DataObject1
{
public int Id { get; set; }
public string Code { get; set; }
}
public class DataObject2
{
public int Id { get; set; }
public string Code { get; set; }
public string ActiveFlag { get; set; }
}
Here is my Test Code:
var data = new DataObject1() { Id = 10, Code = "Test" };
var source = new Source<DataObject1> { Value = data };
var dest = mapper.Map<Source<DataObject1>, Destination<DataObject2>>(source);
Assert.AreEqual(dest.Value.Id, 10);
Your mapping will succeed if you map DataObject1 to DataObject2 when you configure your mapper like so:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Source<>), typeof(Destination<>));
cfg.CreateMap<DataObject1, DataObject2>();
});
... or are you trying to avoid having to know at compile time that you may need to map DataObject1 to DataObject2 at all?
Related
I got a very strange problem,when i add a property named [IdTypeCode] with decimal type in
Destination,the Map method will throw an exception. Does anyone know how to solve it? Thanks.
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
public decimal IdTypeCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(c => c.CreateMap<Source, Destination>());
var mapper = configuration.CreateMapper();
//will throw an exception
var dest = mapper.Map<Destination>(new Source
{
Id = 1
});
}
}
I believe this is a side-effect of AutoMapper attempting to "flatten" the source Id property into the destination object.
Since your destination has the property IdTypeCode, AutoMapper will try to find a matching value from the source. And in this case, it seems to be picking up on the Type.GetTypeCode() method on the Source.Id property, and hence trying to map a System.TypeCode to a decimal. This can be seen in the exception that's thrown:
Destination Member: IdTypeCode
---> AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
TypeCode -> Decimal
System.TypeCode -> System.Decimal
This can be verified by changing the type of Destination.IdTypeCode to System.TypeCode:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
public TypeCode IdTypeCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(c => c.CreateMap<Source, Destination>());
var mapper = configuration.CreateMapper();
//will throw an exception
var dest = mapper.Map<Destination>(new Source
{
Id = 1
});
Console.WriteLine(dest.IdTypeCode);
}
}
which causes the mapping to succeed and the Destination.IdTypeCode getting mapped with the value Int32.
With that said, aside from changing the name of the property, one easy solution is just to ignore that property on the destination:
var configuration = new MapperConfiguration(c =>
{
c.CreateMap<Source, Destination>()
.ForMember(d => d.IdTypeCode, opt => opt.Ignore());
});
according to Lucian's answer, i change my code as below,it works well
public class Source
{
public string Card { get; set; }
}
public class Destination
{
public string Card { get; set; }
public decimal CardTypeCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(configure =>
{
//set this func
configure.ShouldMapMethod = _ => false;
configure.CreateMap<Source, Destination>();
});
var mapper = configuration.CreateMapper();
var dest = mapper.Map<Destination>(new Source
{
Card = "1"
});
}
}
I'm trying to use AutoMapper (v6.1.1) to flatten out a class containing further nested classes.
For reasons™ I'm unable to alter these classes, so altering names is not possible.
Bearing this in mind I can solve it using the static Mapper like so:
// This is the nested user, it has a further nested class
public class NestedUser
{
public long id { get; set; }
public string type { get; set; }
public Attributes attributes { get; set; }
}
public class Attributes
{
public string first_name { get; set; }
public string last_name { get; set; }
public string name { get; set; }
}
// This is the flattened representation
public class FlattenedUser
{
public long id { get; set; }
public string type { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string name { get; set; }
}
// Create a nested user
var nested = new NestedUser
{
id = 1,
type = "Contact",
attributes = new Attributes
{
first_name = "Equals",
last_name = "Kay",
name = "Equalsk"
}
};
// Use the static Mapper to flatten
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
Mapper.AssertConfigurationIsValid();
var flattened = Mapper.Map<FlattenedUser>(nested);
The object flattened now has all of its properties correctly populated.
For more reasons™ I want to use an instance of AutoMapper, something like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
config.AssertConfigurationIsValid();
var flattened = config.CreateMapper().Map<FlattenedUser>(nested);
My issue is the line .ConstructUsing(s => ... ));, it refers to the static Mapper and so it throws a runtime exception:
System.InvalidOperationException: 'Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.'
I don't want to use .ForMember(...) for each nested property as it defeats the object of what I'm trying to do.
Now I'm stuck. Is it possible to flatten out the nested class using an instance of AutoMapper as opposed to the static way?
I see 2 options to resolve the issue, depending on your case one should fit:
First: if you want to continue using static mapper for nested object than you have to move registration of nested objects to static configuration:
Mapper.Initialize(cfg=> cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None));
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
Second: Use ResolutionContext
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing((s, r) => r.Mapper.Map<FlattenedUser>(s.attributes));
});
I found this link on Github which shows how to set up a mapper configuration when an inner map is nested within an outer one.
https://github.com/AutoMapper/AutoMapper/wiki/Nested-mappings
However this isn't working for me probably as I have a bit of a difference: My Inner object is actually a Collection of InnerSource,
i.e.
public class OuterSource
{
public int Value { get; set; }
public Collection<InnerSource> Inners { get; set; }
}
public class InnerSource
{
public int OtherValue { get; set; }
}
and
public class OuterDest
{
public int Value { get; set; }
public Collection<InnerDest> Inners { get; set; }
}
public class InnerDest
{
public int OtherValue { get; set; }
}
The solution in the link gives this code to create the MapperConfiguration
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<OuterSource, OuterDest>();
cfg.CreateMap<InnerSource, InnerDest>();
});
// setup object data
var mapper = config.CreateMapper();
var dest = mapper.Map<OuterSource, OuterDest>(source);
But then when I run this, I get an AutoMapperMappingException
(Inner Exception =
Argument 'value' must not be null.
Parameter name: value )
How can I create a nested mapping like this but where the inner object is a Collection? Note my real case is a bit more complex than the code provided above (with several .ForMember() calls)
I need to map List<Source> to List<Dest>, The issue is that Source contains NestedObject inside it & Dest also contains a NestedObject inside it. I need to map these two also while my List is being mapped. I have tried everything but either the nested object always remains null and doesnt get mapped or I get the following exception:
Missing type map configuration or unsupported mapping. Mapping types:
.....
Structure of Source & Destinations:
Source:
public class Source {
public string Prop1 { get; set; }
public CustomObjectSource NestedProp{ get; set; }
}
public class CustomObjectSource {
public string Key { get; set; }
public string Value{ get; set; }
}
Destination:
public class Destination {
public string Prop1 { get; set; }
public CustomObjectDest NestedProp{ get; set; }
}
public class CustomObjectDest {
public string Key { get; set; }
public string Value{ get; set; }
}
What I have tried:
I have the following code, tried several other approaches also but to no avail:
var config = new MapperConfiguration(c =>
{
c.CreateMap<Source, Destination>()
.AfterMap((Src, Dest) => Dest.NestedProp = new Dest.NestedProp
{
Key = Src.NestedProp.Key,
Value = Src.NestedProp.Value
});
});
var mapper = config.CreateMapper();
var destinations = mapper.Map<List<Source>, List<Destination>>(MySourceList.ToList());
I am stuck with this for days, Kindly help.
You got to map the CustomObjectSource to CustomObjectDest as well.
This should do it:
var config = new MapperConfiguration(c =>
{
c.CreateMap<CustomObjectSource, CustomObjectDest>();
c.CreateMap<Source, Destination>()
.AfterMap((Src, Dest) => Dest.NestedProp = new CustomObjectDest
{
Key = Src.NestedProp.Key,
Value = Src.NestedProp.Value
});
});
var mapper = config.CreateMapper();
var MySourceList = new List<Source>
{
new Source
{
Prop1 = "prop1",
NestedProp = new CustomObjectSource()
{
Key = "key",
Value = "val"
}
}
};
var destinations = mapper.Map<List<Source>, List<Destination>>(MySourceList.ToList());
I have a weird situation where I have objects and Lists of objects as part of my entities and contracts to interface with a third-party service. I'm going to try to see if I can replace the actual object class with something more specific in the entities and contracts to get around this, but I am curious if there is a way to get AutoMapper to handle this as is.
Here are some dummy classes:
public class From
{
public object Item { get; set; }
}
public class FromObject
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
And the quick replication:
Mapper.CreateMap<From, To>();
Mapper.CreateMap<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // FromObject
Basically, the question is this: Is there a way to get AutoMapper to understand that from.Item is a FromObject and apply the mapping to ToObject? I'm thinking there's probably not a way to make it automatic, since there's nothing that would indicate that to.Item has to be a ToObject, but is there a way to specify during the CreateMap or Map calls that this should be taken into account?
I don't think there is an "automatic" way of doing it, since AutoMapper won't be able to figure out that From.Item is FromObject and To.Item is ToObject.
But, while creating mapping, you can specify that
Mapper.CreateMap<FromObject, ToObject>();
Mapper.CreateMap<From, To>()
.ForMember(dest => dest.Item, opt => opt.MapFrom(src => Mapper.Map<ToObject>(src.Item)));
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject
If you're willing to use an additional interface, this can be accomplished using Include. You can't just map object to object in this fashion, though.
public class From
{
public IItem Item { get; set; }
}
public class FromObject : IItem
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
public interface IItem
{
// Nothing; just for grouping.
}
Mapper.CreateMap<From, To>();
Mapper.CreateMap<IItem, object>()
.Include<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject