Automapper 2 - unflattening entity to VM - c#

I have a flat POCO Entity and a structured View Model (that is used for rendering a form with custom EditorFor helpers).
Is there any easier way in Automapper 2 to do something like that:
cfg.CreateMap<TelcoDemand, TelcoDemandViewModel>()
.ForMember(d => d.Person, opt => opt.ResolveUsing(s => new PersonViewModel
{
FirstName = s.PersonFirstName,
LastName = s.PersonLastName,
Phone = s.PersonPhone,
Email = s.PersonEmail
}))
.ForMember(d => d.AddressContact, opt => opt.ResolveUsing(s => new AddressViewModel
{
City = s.AddressContactCity,
HouseNumber = s.AddressContactHouseNumber,
Street = s.AddressContactStreet,
Zip = s.AddressContactZip
}))
.ForMember(d => d.AddressCourier, opt => opt.ResolveUsing(s => new AddressViewModel
{
City = s.AddressCourierCity,
HouseNumber = s.AddressCourierHouseNumber,
Street = s.AddressCourierStreet,
Zip = s.AddressCourierZip
}))
.ForMember(d => d.Address, opt => opt.ResolveUsing(s => new AddressViewModel
{
City = s.AddressCity,
HouseNumber = s.AddressHouseNumber,
Street = s.AddressStreet,
Zip = s.AddressZip
}))
What I am trying to achieve is to unflatten Person and Address fields into a viewmodel that then gets rendered using a EditorFor.
Flattening it back to database works great, but there is no way I could get the unflattening working by convention...
Edit
I have also a IgnoreAllUnexisting extension used here, it's implemented like that:
public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
expression.ForMember(property, opt => opt.Ignore());
}
expression.ForAllMembers(opt => opt.Condition(src => !src.IsSourceValueNull));
return expression;
}
Anyway I also tried it without that extension, and the result was just the same - bunch of null values in the complex types.

Related

How to configure automapper so that it won't override the properties which are not defined in mapping

I am mapping one class property with two different other classes using auto mapper like below code:
var emp = map.Map<EmployeeAddressDto, Employee>(employeeAddressDto);
The above mapping gives me objects like the below:
Address1 = "some xyz address1"
Address2 = "some xyz address2"
Phone = "0000000000"
and then mapping the output object with EmployeeOtherAddressDto
map.Map<EmployeeOtherAddressDto, Employee>(emp);
The above mapping changes my out to this:
Address1 = "some other xyz address1"
Address2 = "some other xyz address2"
Phone = null
But I want it to be like the below:
Address1 = "some other xyz address1"
Address2 = "some other xyz address2"
Phone = "0000000000"
When the above mapping occurs it makes the phone no null but I want it to remain as it was defined on the previous mapping, although I want to override Address1 and Address2 fields.
Here is my mapping:
CreateMap<EmployeeAddressDto, Employee>()
.ForMember(dest => dest.Address1, options => options.MapFrom(src => src.Address1))
.ForMember(dest => dest.Address2, options => options.MapFrom(src => src.Address2))
.ForMember(dest => dest.Phone, options => options.MapFrom(src => src.Phone))
.ValidateMemberList(MemberList.None)
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
CreateMap<EmployeeOtherAddressDto, Employee>()
.ForMember(dest => dest.Address1, options => options.MapFrom(src => src.Address1))
.ForMember(dest => dest.Address2, options => options.MapFrom(src => src.Address2))
.ValidateMemberList(MemberList.None)
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
I want to do this in my mapping profile because I am using it in many places and do not want to update the phone no without using a mapper.
Please try this method for config your mapping.
AfterMap<>()
CreateMap<EmployeeOtherAddressDto, Employee>().AfterMap<EmployeeMappingAction>()
.ForMember(dest => dest.Address1, options => options.MapFrom(src => src.Address1))
.ForMember(dest => dest.Address2, options => options.MapFrom(src => src.Address2))
.ValidateMemberList(MemberList.None)
you can check your filed after mapping to check is it null or not.
public class EmployeeMappingAction: IMappingAction<EmployeeOtherAddressDto,Employee>
{
public void Process(EmployeeOtherAddressDto source, Employee destination, ResolutionContext context)
{
if (source.Phone == null)
{
destination.Phone = "";
}
}
}

Handle multiple mappings for same source and destination object

I have Source class and Destination class to map. Destination class is a generic class such as Person which will be used as a field in one of my class (MainClass) in various fields e.g Father,Mother,Brother etc. How can I map data coming from source to destination which is present for Father,Mother etc.
I can create
CreateMap<Source, MainClass>()
.ForMember(dest => dest.Mother, m => m.MapFrom(source => source))
.ForMember(dest => dest.Father, m => m.MapFrom(source => source))
.ForMember(dest => dest.Brother, m => m.MapFrom(source => source));
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, m => m.MapFrom(source => source.motherName))
.ForMember(dest => dest.ID, m => m.MapFrom(source => source.motherId))
.ForMember(dest => dest.Address, m => m.MapFrom(source => source.motherAddress));
but how can I handle mapping for father,brother etc. to achieve
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, m => m.MapFrom(source => source.FatherName))
.ForMember(dest => dest.ID, m => m.MapFrom(source => source.FatherId))
.ForMember(dest => dest.Address, m => m.MapFrom(source => source.FatherAddress));
Ok so this is an untested Code from an notepad editor :D
you can try this and change it in the way you need it. This will not work from the very beginning!
opt.MapFrom(source => SetName(source, "Mother")))
private object SetName(Person y, string personState)
{
Person person = new Person();
var properties = DictionaryFromType(y);
foreach(var property in properties)
{
if(property.Key.ToLower().Contains(personState.ToLower()))
{
// you should make the real mapping to id here. This is just example code on how it could work
PropertyInfo propertyInfo = person.GetType().GetProperty(property.Key);
propertyInfo.SetValue(person, Convert.ChangeType(property.Value, propertyInfo.PropertyType), null);
}
}
return person;
}
public static Dictionary<string, object> DictionaryFromType(object atype)
{
if (atype == null) return new Dictionary<string, object>();
Type t = atype.GetType();
PropertyInfo[] props = t.GetProperties();
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (PropertyInfo prp in props)
{
object value = prp.GetValue(atype, new object[] { });
dict.Add(prp.Name, value);
}
return dict;
}
may be you have to debug a little bit to make it work but somehow like this you can make it.
It is possible that there are better solutions with automapper, but i only had this in my mind for the moment.
Hope this can help you even if this is not a finished answer! (Sorry my time is low atm)

AutoMapper Map If Not Null, Otherwise Custom Convert

Here's my code:
Mapper.CreateMap<Foo, Foo2>()
.ForMember(dest => dest.Bar, opt => opt.MapFrom(src => src.Bar == null ? new BarViewModel() : src.Bar))
Basically, "BarViewModel" has a parameterless ctor which sets up properties in the class.
So i'm trying to say to AutoMapper:
If the value is null, then use the ctor for the class. otherwise use the mapping you have in place
The above is giving me a C# compiler error. And i'm guessing a cast wouldn't work either.
So is there a AutoMapper trick to do this?
Worst case i could remove that mapping for that property, and just do:
var mapped = Mapper.Map<Foo,Foo2>(src);
if (mapped.Bar == null) mapped.Bar = new BarViewModel();
But that's a tad ugly.
Ideas?
You can use custom value resolver. The following should work:
Mapper.CreateMap<Foo, Foo2>()
.ForMember(dest => dest.Bar, opt => opt.ResolveUsing(src => src.Bar == null ? new Bar() : Mapper.Map<Bar,Bar2>(src.Bar)))
Now you can use .NullSubstitute() to replace NULL value to some custom value in Automapper, e.g.:
CreateMap<SMModel, VM_SMModel>()
.ForMember(d => d.myDate, o => o.NullSubstitute(new DateTime(2017,12,12)));
As of Automapper 8, ResolveUsing is no longer an option but inline Func's, IValueResolver and IMemberValueResolver are 😊.
Inline Func Example
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Foo, FooViewModel>()
.ForMember(dest => dest.BarViewModel,
opt => opt.MapFrom((src, dest) =>
{
if (src.Bar == null)
return new BarViewModel ();
return Mapper.Map<Bar, BarViewModel>(src.Bar);
}));
cfg.CreateMap<Bar, BarViewModel>();
});
IMemberValueResolver Example
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Foo, FooViewModel>()
.ForMember(dest => dest.BarViewModel,
opt => opt.MapFrom<NullBarResolver, Bar>(src => src.Bar));
cfg.CreateMap<Bar, BarViewModel>();
});
public class NullBarResolver : IMemberValueResolver<object, object, Bar, BarViewModel>
{
public BarViewModel Resolve(object source, object destination, Bar sourceMember,
BarViewModel destMember, ResolutionContext context)
{
if (sourceMember == null)
return new BarViewModel();
return Mapper.Map(sourceMember, destMember);
}
}
There's some good documentation on Custom Value Resolvers here.
Working demo
I don't get a compiler error for the following:
public class Foo
{
public Bar Bar { get; set; }
}
public class Foo2
{
public Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public Bar()
{
Id = 3;
}
}
CreateMap<Foo, Foo2>()
.ForMember(
dest => dest.Bar,
opt => opt.MapFrom(src => src.Bar == null ? new Bar() : src.Bar));
...so I'm wondering if the problem is not actually with your mapping?
This can be done with the PreCondition() method. Here's an extension method I wrote to do this more easily:
public static IMappingExpression<TSource, TDestination> MapIf<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> map, Expression<Func<TDestination, object>> selector,
Func<TSource, bool> mapIfCondition, Expression<Func<TSource, object>> mapping)
{
map.ForMember(selector, c =>
{
c.MapFrom(mapping);
c.PreCondition(mapIfCondition);
});
return map;
}
Usage Example:
//if settings are null coming from the sender, then ignore them and keep the current settings
CreateMap<PresentationView, Presentation>()
.MapIf(x => x.Settings, x => x.Settings is not null, v => v.Settings!)

Using Automapper to make full copy of an object with IList properties

So I need to make copies of objects. I have a model here "place" that has a IList HasAndBelongsToMany Property that is being a pain. I need to take the field property and copy it too but it copies the reference only. Here is what i have
public class place : ActiveRecordBase<place>
{
public place() { }
private int place_id;
[PrimaryKey("place_id")]
virtual public int id
{
get { return place_id; }
set { place_id = value; }
}
private IList<fields> Fields;
[HasAndBelongsToMany(typeof(fields), Lazy = true, Table = "place_to_fields", ColumnKey = "place_id", ColumnRef = "field_id", NotFoundBehaviour = NotFoundBehaviour.Ignore, Cascade = ManyRelationCascadeEnum.AllDeleteOrphan)]
virtual public IList<fields> field
{
get { return Fields; }
set { Fields = value; }
}
}
And use automapper like this
place org = ActiveRecordBase<place>.Find(id);
Mapper.Reset();
Mapper.CreateMap<place, place>().ForMember(dest => dest.id, o => o.Ignore())
.ForMember(dest => dest.field, o => o.Ignore())
;
place copy = new place();
Mapper.Map(org, copy);
copy.SaveAndFlush();
Which works because i'm skiping the field. What i was hoping for was something more like:
Mapper.CreateMap<place, place>().ForMember(dest => dest.id, o => o.Ignore())
.ForMember(dest => dest.field.id, o => o.Ignore())
;
See the first line with .ForMember(dest => dest.id, o => o.Ignore()) is so that I don't copy the reference id for the place object. I need to do the same for the place property field. I need to ignore the id's and make new entries with the same values on the rest of its properties
You need to create mapping for field type and add "Ignore" option for field's id like you've alredy done for place type.
Mapper.CreateMap<fields, fields>().ForMember(dest => dest.id, o => o.Ignore());
Mapper.CreateMap<place, place>().ForMember(dest => dest.id, o => o.Ignore());

C# Function that accepts string parameters: How to refactor to use something strongly typed?

I have this C# code that works just fine, which grabs any two fields and puts them in a list for a Drop Down List:
var myDDL = GetDDLValues<Product>( myContact, "contactid", "companyname");
I would like it to take the two string parameters in something other than strings. This would be really nice to do:
GetDDLValues<Product>( myContact, p => p.contactid, p => p.companyname)
This is the code it calls (reflection by sweko Thanks!):
private object GetProperty(object obj, string propertyName)
{
PropertyInfo pi = obj.GetType().GetProperty(propertyName);
object value = pi.GetValue(obj, null);
return value;
}
public IList<DDLValues> GetDDLValues<T>(IList<T> objectListToMap,
string textProperty, string valueProperty)
{
if( objectListToMap != null && objectListToMap.Count > 0)
{
Mapper.CreateMap< T, DDLValues>()
.ForMember( dest => dest.text,
opt => opt.MapFrom(src => textProperty))
.ForMember( dest => dest.value,
opt => opt.MapFrom(src => valueProperty));
return Mapper.Map<IList<T>, IList<DDLValues>>(objectListToMap);
}
else
{
return null;
}
}
To build a dynamic query from string:
public class Product
{
public long ID { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
}
static void Main(string[] args)
{
List<Product> products = (from i in Enumerable.Range(1, 10)
select new Product { ID = i, Name = "product " + i, Date = DateTime.Now.AddDays(-i) }).ToList(); //the test case
const string SortBy = "Date"; // to test you can change to "ID"/"Name"
Type sortType = typeof(Product).GetProperty(SortBy).PropertyType; // DateTime
ParameterExpression sortParamExp = Expression.Parameter(typeof(Product), "p"); // {p}
Expression sortBodyExp = Expression.PropertyOrField(sortParamExp, SortBy); // {p.DateTime}
LambdaExpression sortExp = Expression.Lambda(sortBodyExp, sortParamExp); // {p=>p.DateTime}
var OrderByMethod = typeof(Enumerable).GetMethods().Where(m => m.Name.Equals("OrderBy") && m.GetParameters().Count() == 2).FirstOrDefault().MakeGenericMethod(typeof(Product), sortType);
var result = OrderByMethod.Invoke(products, new object[] { products, sortExp.Compile() });
}
tvanfosson is right that a Select() is simplest; if you want to use AutoMapper, you want something like this..
public IList<DDLValues> GetDDLValues<T>(IList<T> objectListToMap,
Func<T, string> textSelector, Func<T, string> valueSelector)
{
if( objectListToMap == null || objectListToMap.Count == 0)
return null;
Mapper.CreateMap< T, DDLValues>()
.ForMember( dest => dest.text,
opt => opt.MapFrom(textSelector))
.ForMember( dest => dest.value,
opt => opt.MapFrom(valueSelector));
return Mapper.Map<IList<T>, IList<DDLValues>>(objectListToMap);
}
If DDLValues has a constructor that takes the text and value properties it should be relatively simple.
IList<DDLValues> GetDDLValues<T>( IList<T> source, Func<T,DDLValues> selector )
where T : class
{
return source.Select( selector )
.ToList();
}
Called as
var ddlList = GetDDLValues( contacts, p => new DDLValues( c.Name, p.ContactID.ToString() ) );
This presumes that DDLValues has a constructor like:
public DDLValues( string text, string value ) { ... }
If you're pulling from the database in your source, then you may also need to materialize the query using ToList() before you apply the selector to ensure it doesn't try to convert the selector into a SQL expression.

Categories

Resources