AutoMapper: class properties, map ISet<object> to HashSet<Object> - c#

I have defined a mapping to/from my DTO object, the properties on one versus the other match excatly except that the DTO Object has collections defined as ISet and the non DTO object has those collectiond defined as HashSet . I've noticed a significant performance hit mapping from DTO -> Non DTO, vs the other way.
AutoMapper seems to have trouble going from Interface from concrete class, I'm wondering if I'm missing something in a mapping, or configuration somewhere to be more explicit. This paradigm exists across our code base, but for my object in question I can map 2k objects from the DTO in about 8 seconds, and I can map the exact same objects to the DTO in about .1 seconds
class ExampleDTO
{
public int Id;
public enum Type;
public DateTime creationTime;
public ISet<string> StringThings;
public ISet<int> IntThings;
public ISet<double> DoubleThings;
}
class Example
{
public int Id;
public enum Type;
public DateTime creationTime;
public HashSet<string> StringThings;
public HashSet<int> IntThings;
public HashSet<double> DoubleThings;
}
Mapping:
CreateMap<ExampleDTO, Example>();
CreateMap<Example, ExampleDTO>();

We found that upgrading Automapper (to version 6.0.2) would be the way to go in our case. Using the updated AutoMapper and the same objects and mappings listed above we saw the ExampleDTO->Example object mapped in 1.57 seconds, and the reverse case mapped in 1.86 seconds. I'm not overly happy posting an answer that says use the upgrade, so I'll post a few options that gave some modest gains, and if anyone else has an actual answer, I'll gladly mark that one.
I tried creating a mapping for the ISet HashSet and this was about twice as fast as without that specified mapping, I cannot remember exactly what I did here, but I found it on the googles.
The other option I tried, was creating Non Mappable HashSets on the DTO object that simply returned the ISet. This was about 3x faster, but still not close to the performance gained by upgrading.
class ExampleDTO
{
public int Id;
public enum Type;
public DateTime creationTime;
public ISet<string> StringThings;
[IgnoreMap]
public HashSet<string> StringThingsHash
{
get
{
return StringThings;
}
}
public ISet<int> IntThings;
[IgnoreMap]
public HashSet<int> IntThingsHash
{
get
{
return IntThings;
}
}
public ISet<double> DoubleThings;
[IgnoreMap]
public HashSet<double> DoubleThingsHash
{
get
{
return DoubleThings;
}
}
and I used the following mapping
CreateMap<ExampleDTO, Example>()
.ForMember(dest => dest.StringThings, opts => opts.MapFrom(src => src.StringThingsHash)
.ForMember(dest => dest.IntThings, opts => opts.MapFrom(src => src.IntThingsHash)
.ForMember(dest => dest.DoubleThings, opts => opts.MapFrom(src => src.DoubleThingsHash);
CreateMap<Example, ExampleDTO>();

Related

Unable to resolve mapper issue using Automapper

I have two classes in my code except one data member everything is same. But, not getting how to resolve this issue using Automapper. Can someone please throw some light on this.
Class A{ public string Id; public List Values; };
Class B{ public string Id; public List Items; };
Class C{ public string Value; public string Name; public string Dept; };
CreateMap<A, B>(); - This is giving following error while doing unit test, Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
Explicit AutoMapper Property Mapping
CreateMap<A,B>()
.ForMember(dest => dest.Items,
opt => opt.MapFrom(src => src.Values));
Reference

Automapper : how to eleminate repetitive mapping?

I have following structure for tables. The two tables have a lot of common properties over 20 im just listing a two. Also I have 10 tables similar to this. This is how the tables are in the database. There is over 10 concrete tables with similar properties and are not connected to each other in any way. I am using POCO generator to generate the classes from my database.
public class A
{
public string name {get;set;}
public string address {get;set;}
public string AId {get;set;}
}
public class B
{
public string name {get;set;}
public string address {get;set;}
public string BId {get;set;}
}
I have following viewModels:
public class BaseViewModel
{
public string Fullname {get;set;}
public string Fulladdress {get;set;}
}
public class AviewModel : BaseViewModel
{
public string AId {get;set;}
}
public class BViewModel : BaseViewModel
{
public string BId {get;set;}
}
when I create mapping i have to repeat all this for each viewModel that I have created.
config.CreateMap<A, AviewModel>()
.ForMember(dest => Fulladdress, opt => opt.MapFrom(src =>.address))
.ForMember(dest => Fullname, opt => opt.MapFrom(src =>.name)).ReverseMap();
config.CreateMap<B, BviewModel>()
.ForMember(dest => Fulladdress, opt => opt.MapFrom(src =>.address))
.ForMember(dest => Fullname, opt => opt.MapFrom(src =>.name)).ReverseMap();
Is it possible to reduce the repetitive mappings that I might potentially have to do ?
You can move the common mapping code to a helper generic method. You will constrain the TDestination type to be a class derived from BaseViewModel, thus allowing to access the destination members in ForMember method. And for source mapping you will use the MapFrom overload accepting string property name:
public static class CommonMappings
{
public static IMappingExpression<TSource, TDestination> MapToBaseViewModel<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map)
where TDestination : BaseViewModel
{
return map
.ForMember(dest => dest.Fulladdress, opt => opt.MapFrom("address"))
.ForMember(dest => dest.Fullname, opt => opt.MapFrom("name"));
}
}
Then you can use it like this:
config.CreateMap<A, AViewModel>()
.MapToBaseViewModel()
// ... specific mappings
.ReverseMap();
config.CreateMap<B, BViewModel>()
.MapToBaseViewModel()
// ... specific mappings
.ReverseMap();
Update: It turns out that automatic reverse mapping in the latest at this time AutoMapper 6.1.1 works for the lambda overload of MapFrom, but not for the string overload (in AutoMapper 5 it doesn't work at all). So until it gets fixed, you can use the following MapFrom(string) replacement:
public static class AMExtensions
{
public static void From<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt, string name)
{
var parameter = Expression.Parameter(typeof(TSource), "src");
var body = Expression.PropertyOrField(parameter, name);
var selector = Expression.Lambda(body, parameter);
opt.MapFrom((dynamic)selector);
}
}
Which means you'll need to replace MapFrom calls in the original solution with From, because we can't give the extension method the same name since it has less priority than the concrete interface method.
Probably too much effort compared to base class approach. But useful in case you can't control the design of the entity classes.
You need a base class for the source classes. You create a map between the base source class and the destination class. You include that map in the map for the derived classes. That would allow you to reuse the configuration. The docs. For simple cases you can use As instead of Include.
You set an attribute on properties on your models.
This contains the name of the property it maps from on a source object.
Then you make a generic method that accepts the target object and source object, that finds the customattribute on the property where the attribute was set, and the property on the target object (or vice versa) and then sets the value.
You can even handle nested objects by asking if its a class property or not.

Automapper: Mapping onto existing nested complex property

Is it possible to tell AutoMapper during map creating to map onto existing instance of nested property?
Let's suppose I've got a class:
public class SomeClass
{
public int Id {get; set;}
public Complex Settings {get; set;}
}
public class Complex
{
public int Id { get; set;}
public string SomeText { get; set;}
}
I want to create map from SomeClass to SomeClass and use it to map properties onto existing instance.
Mapper.CreateMap<SomeClass, SomeClass>()
.ForMember(src => src.Settings, opts => opts.MapFrom(src => Mapper.Map<Complex, Complex>(src));
Mapper.CreateMap<Complex, Complex>();
Mapper.Map<SomeClass, SomeClass>(a, b);
Where a and b are instances of SomeClass. The problem is this solution maps properties onto existing instance but creates new instance of Complex instead of mapping a.Complex onto existing b.Complex.
Is it possible to configure AutoMapper to get desired behavior?
(It's causing me a lot of problems with Entity Framework).
I figured it out myself. Solution was pretty simple.
Correct map creation looks like this:
Mapper.CreateMap<SomeClass, SomeClass>()
.ForMember(src => src.Settings, opts => opts.Ignore())
.AfterMap((src, dst) => Mapper.Map<TestSettings,TestSettings>(src.TestSettings, dst.TestSettings);
Mapper.CreateMap<Complex, Complex>();

Mapping multiple source properties to a single destination property

I'd like to know if there would be to a way to handle this kind of scenario with some custom type or value resolver.
public class SuperDateTime
{
public DateTimeOffset Date { get; set; }
public string Timezone { get; set; }
}
public class Entity
{
public DateTimeOffset CreationDate { get; set; }
public string CreationDateZone { get; set; }
public DateTimeOffset EndDate { get; set; }
public string EndDateZone { get; set; }
}
public class Model
{
public SuperDateTime CreationDate { get; set; }
public SuperDateTime EndDate { get; set; }
}
When i have a SuperDateTime in the destination object, i'd like to instantiate this object with the associated DateTimeOffset and the timezone string in the source object.
Of course what i'd like to do is to make something generic, so not going thought the MapFrom in each CreateMap of every Entity
I tried to do it with a custom TypeConverter but it supports only a SourceType -> DestinationType
In my case i have a string and DateTimeOffset that has to create a SuperDateTime
In addition to what LiamK is suggesting, the next possible improvement is to write a helper method for doing .MapFrom. Depending on your requirements it can be simple or complex. I'm going to offer a simple one that makes a lot of assumptions, but you can modify and optimize it to suit your possible requirements.
static IMappingExpression<TFrom, TTo> MapSuperDateTime<TFrom, TTo>(
this IMappingExpression<TFrom, TTo> expression,
Expression<Func<TTo, object>> dest)
{
var datePropertyName = ReflectionHelper.FindProperty(dest).Name;
var timezomePropertyName = datePropertyName + "Zone";
var fromType = typeof (TFrom);
var datePropertyGetter = fromType.GetProperty(datePropertyName).ToMemberGetter();
var timezonePropertGetter = fromType.GetProperty(timezomePropertyName).ToMemberGetter();
return expression.ForMember(dest, opt => opt.MapFrom(src => new SuperDateTime
{
Date = (DateTimeOffset)datePropertyGetter.GetValue(src),
Timezone = (string)timezonePropertGetter.GetValue(src)
}));
}
And then you can specify your mapping like this:
Mapper.CreateMap<Entity, Model>()
.MapSuperDateTime(dest => dest.CreationDate)
.MapSuperDateTime(dest => dest.EndDate);
The assumption is that if your Entity DateTimeOffset is called bla, then your corresponding Entity string is called blaZone, and your Model SuperDateTime is called bla.
You can use the Customer Resolver for this. I used custom resolver for getting an object from int something like this;
Lets say you are creating a mapping like this(Althoug you didn't show how you are creating it):
Mapper.CreateMap<YourSource, YourDestination>()
.ForMember(x => x.DateTimeOffset, opt => opt.ResolveUsing(new DateTimeOffsetResolver(loadRepository)).FromMember(x => x.timezone));
And this how your resolver will look like:
public class DateTimeOffsetResolver : ValueResolver<string, DateTimeOffset>
{
private DatabaseLoadRepository loadRepository;
public personIdResolver(DatabaseLoadRepository repo)
{
this.loadRepository = repo;
}
protected override DateTimeOffset ResolveCore(string timeZone)
{
//Your logic for converting string into dateTimeOffset goes here
return DateTimeOffset; //return the DateTimeOffset instance
}
}
You can remove all the code related to Nhibernate Repository if you not need to access it.
You can further read about custom resolvers here
The short answer to you question is 'No', there isn't way to use a custom value resolver to map < string, DateTimeOffset > => SuperDateTime and avoid the repeated .MapFrom calls. In your example above, such a value resolver wouldn't be able to distinguish which strings and DateTimeOffsets went together during mapping.
Not sure if you have the .MapFrom code yourself, but if not, below is the best solution to your problem:
Mapper.CreateMap<Entity, Model>()
.ForMember(
dest => dest.CreationDate,
opt => opt.MapFrom(
src => new SuperDateTime()
{
Date = src.CreationDate,
TimeZone = src.CreationDateZone
};
));
If you really want to avoid excessive MapFrom declarations, see if there's a way to leverage mapping inheritance here.
EDIT: Modified instantiation of SuperDateTime to match the provided source code.
After sleeping on it, here is an alternative that feels more generic.
Assume, you want to do something like this:
Mapper.CreateMap<Entity, Model>()
.ForMemberType((member,src) => new SuperDateTime
{
Date = (DateTimeOffset)GetPropertyValue(src, member),
Timezone = (string)GetPropertyValue(src, member+"Zone")
});
This looks a bit nicer then my first answer, because here you specify that you want to map all members of SuperDateTime at once. (The type is inferred from the return type of the lambda.) Really, similar to ForAllMembers that AutoMapper already has. The only problem that you cannot use standard memberOptions as IMemberConfigurationExpression<TSource> does not give you access to the member currently being configured. For brevity, I removed the memberOptions parameter from ForMemberType signature completely, but it is very easy to add it back if you need that (that is to set some other options too - see example here).
So in order to be able to write the above all you need is GetPropertyValue method, that can look like this:
public object GetPropertyValue(object o, string memberName)
{
return o.GetType().GetProperty(memberName).ToMemberGetter().GetValue(o);
}
And the ForMemberType method itself, which would look like that:
public static IMappingExpression<TSource, TDestination> ForMemberType<TSource, TDestination, TMember>(
this IMappingExpression<TSource, TDestination> expression,
Func<string, TSource, TMember> memberMapping
)
{
return new TypeInfo(typeof(TDestination))
.GetPublicReadAccessors()
.Where(property => property.GetMemberType() == typeof(TMember))
.Aggregate(expression, (current, property)
=> current.ForMember(property.Name,
opt => opt.MapFrom(src => memberMapping(property.Name, src))));
}
And that's it. To avoid recompiling the property getter every time, you might want to add a very simple caching layer that compiles (executes ToMemberGetter) once for each type and remembers the result somewhere. Using AutoMapper own DictonaryFactory and then IDictionary.GetOrAdd is probably the most straight forward way of doing this:
private readonly IDictionary<string, IMemberGetter> _getters
= new DictionaryFactory().CreateDictionary<string, IMemberGetter>();
public object GetPropertyValue(object o, string memberName)
{
var getter = _getters.GetOrAdd(memberName + o.GetType().FullName, x => o.GetType()
.GetProperty(memberName).ToMemberGetter());
return getter.GetValue(o);
}

Convert Entity Framework Object to JSON Object

I'm trying to make a generic handler post a JSONJ object based on my entity type SYSTEM_AUDIT_SHEET:
SYSTEM_AUDIT_SHEET sheet = ctx.SYSTEM_AUDIT_SHEET
.Where(s => s.SYSTEM_KEY == system_key_dec)
.Select(s => s)
.OrderByDescending(s => s.AUDIT_SHEET_VERSION)
.First();
HttpContext.Current.Response.Write(serializer.Serialize(sheet));
But I get the following error:
A circular reference was detected while serializing an object of type
'System.Data.Entity.DynamicProxies.SYSTEM_AUDIT_SHEET_521A7B786A51FC0F83641182DD72C8DFE6C082418D30BBB977B403409A74CE17'.
Why can't I convert the entity to JSON?
You cannot convert objects to json that reference themselves as this would create an infinitely long json string.
For example, the following pseudo-code wouldn't work because it sets up a circular reference (Dog >> Bone >> Dog...):
class Dog {
private Bone myBone;
public Dog() {
myBone = new Bone(this);
}
}
class Bone {
private Dog buriedBy;
public Bone(Dog d) {
buriedBy = d;
}
}
There seem to be some good solutions by googling 'json circular reference'. See the top two stack overflow links.
The problem is probably that your SYSTEM_AUDIT_SHEET either contains a property that references instances of type SYSTEM_AUDIT_SHEET, or it contains a property that points to objects that have pointers to SYSTEM_AUDIT_SHEET instances. Serializing such a circle of pointers would result in a serialization process that would never end.
You will need to transform your SYSTEM_AUDIT_SHEET to a type that does not (directly or indirectly) reference itself before doing the serialization. You can create a brand new type and write code to instantiate such a type from your SYSTEM_AUDIT_SHEET (AutoMapper might come in handy here). However, I tend to find that in most cases it is easier to just use an anonymous type:
SYSTEM_AUDIT_SHEET sheet = /*some sheet*/
var json = new {
sheet.Id,
sheet.RevisionNumber,
sheet.Title
};
return serializer.Serialize(json);
EDIT
If you want to use AutoMapper and assuming that your sheet looks something like
class SYSTEM_AUDIT_SHEET
{
public int Id { get; set; }
public SYSTEM_AUDIT_SHEET SomeOtherAuditSheet { get;set;}
public string Title { get;set;}
}
you could create a type like
class JSON_SYSTEM_AUDIT_SHEET
{
public int Id { get; set; }
public int SomeOtherAuditSheetsId { get;set;}
public string Title { get;set;}
}
When your application starts (say, in Application_Start) you configure AutoMapper:
AutoMapper.Mapper.CreateMap<SYSTEM_AUDIT_SHEET, JSON_SYSTEM_AUDIT_SHEET>()
.ForMember(dest => dest.SomeOtherAuditSheetsId, opt => opt.MapFrom(src => src.SomeOtherAuditSheet.Id));
The Id and Title properties will be mapped directly across from SYSTEM_AUDIT_SHEET to JSON_SYSTEM_AUDIT_SHEET because they have the same names in both types. The property SomeOtherAuditSheetsId needs special configuration, because there is no property with that exact name on the source type.
When you want to convert SYSTEM_AUDIT_SHEET to JSON_SYSTEM_AUDIT_SHEET you do:
return AutoMapper.Mapper.Map<SYSTEM_AUDIT_SHEET , JSON_SYSTEM_AUDIT_SHEET >(sheet);
You may want to have a look at AutoMapper's flattening features.
Hope this helps.

Categories