AutoMapper DataServiceCollection to List<string> - automapper

I am attempted to use AutoMapper to map a DataServiceCollection to a list of strings and also create the reverse mapping. Any ideas on how to map a specialized collection like this to another?
Mapper.CreateMap<DataServiceCollection<LocationCountyValue>, List<string>>();

You can create a custom type converter:
public class DataServiceCollectionToStringList : ITypeConverter<DataServiceCollection<LocationCountyValue>, List<string>> {
public List<string> Convert(ResolutionContext context) {
var sourceValue = (DataServiceCollection<LocationCountyValue>) context.SourceValue;
/* Your custom mapping here. */
}
}
Then create the map with ConvertUsing:
Mapper.CreateMap<DataServiceCollection<LocationCountyValue>, List<string>>()
.ConvertUsing<DataServiceCollectionToStringList>();

Thanks to Thiago Sa I created the mapping in both directions like so:
Mapper.CreateMap<DataServiceCollection<CountyValue>, List<string>>()
.ConvertUsing((src) => { return src.Select(c => c.Value).ToList(); });
Mapper.CreateMap<List<string>, DataServiceCollection<CountyValue>>()
.ConvertUsing((src) =>
{
return new DataServiceCollection<CountyValue>(
src.Select(c => new CountyValue() { Value = c }));
});

Related

Automapping dynamic objects ignoring case of property names

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 need generic way to filter IQueryable data and filters are populated as dictionary

I need generic way to filter IQueryable data and filters are populated as dictionary. I have already created method like this.
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
source.Where(m => m.GetType().GetProperty(key).GetValue(m, null).Equals(filterBy[key]));
}
return source.ToList();
}
But its always returning same result.
please find the caller
Dictionary<string, string> dtFilter = new Dictionary<string, string>();
dtFilter.Add("Id", "2");
var res = context.Set<MyEntity>().CustomApplyFilter<MyEntity>(dtFilter);
The Where extension method does not change the content of the IQueryable it is applied to. The return value of the method should be used:
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
source = source.Where(m => m.GetType().GetProperty(key).GetValue(m, null).Equals(filterBy[key]));
}
return source.ToList();
}
UPDATE:
I should have noticed it, my answer so far was applicable to LINQ to Objects only. When using LINQ to Entities, however, there are certain restrictions; only expression that can be converted to an SQL query can be used. Getting properties through reflection is not such an expression obviously.
When this is the case, one possible solution would be to build the ExpressionTree manually.
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
var paramExpr = Expression.Parameter(typeof(T), key);
var keyPropExpr = Expression.Property(paramExpr, key);
var eqExpr = Expression.Equal(keyPropExpr, Expression.Constant(filterBy[key]));
var condExpr = Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
source = source.Where(condExpr);
}
return source.ToList();
}
UPDATE2:
With the comment #Venkatesh Kumar given below, it is apparent that when the underlying type of the field provided is not of type string, this solution fails (with the error message : The binary operator Equal is not defined for the types 'System.Int64' and 'System.String').
One possible way to tackle this problem would be to have a dictionary of types and delegates to use for each such property.
Since this is a static method (an extension method which has to be static), declaring a static Dictionary in class scope would be reasonable:
Let's assume the name of the class in which CustomApplyFilter is declared is SOFExtensions:
internal static class SOFExtensions
{
private static Dictionary<Type, Func<string, object>> lookup = new Dictionary<Type, Func<string, object>>();
static SOFExtensions()
{
lookup.Add(typeof(string), x => { return x; });
lookup.Add(typeof(long), x => { return long.Parse(x); });
lookup.Add(typeof(int), x => { return int.Parse(x); });
lookup.Add(typeof(double), x => { return double.Parse(x); });
}
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
var paramExpr = Expression.Parameter(typeof(T), key);
var keyPropExpr = Expression.Property(paramExpr, key);
if (!lookup.ContainsKey(keyPropExpr.Type))
throw new Exception("Unknown type : " + keyPropExpr.Type.ToString());
var typeDelegate = lookup[keyPropExpr.Type];
var constantExp = typeDelegate(filterBy[key]);
var eqExpr = Expression.Equal(keyPropExpr, Expression.Constant(constantExp));
var condExpr = Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
source = source.Where(condExpr);
}
return source.ToList();
}
}
Other types and proper delegates for them should be added to the lookup Dictionary as required.

Automapper unflatten by prefix

I have an example like this :
class Fields
{
string ContactOneName{get;set;}
string ContactOnePhone{get;set;}
string ContactOneSpouseName{get;set;}
string ContactOneSpousePhone{get;set;}
}
And I would like to map to a model like this:
class Contacts
{
Contact ContactOne {get;set;}
Contact ContactOneSpouse {get;set;}
}
class Contact
{
string Name {get;set;}
string Phone {get;set;}
}
There are lots of fields and I don't want to write a mapping for each field.
Is this possible?
If so how?
NB: This question is almost a duplicate of AutoMapper unflattening complex objects of same type but I want a solution NOT manually mapping everything, because in that case it is not worth using automapper.
You can take this and add:
public static IMappingExpression<TSource, TDestination> ForAllMembers<TSource, TDestination, TMember>(
this IMappingExpression<TSource, TDestination> mapping,
Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> opt)
{
var memberType = typeof(TMember);
var destinationType = typeof(TDestination);
foreach(var prop in destinationType.GetProperties().Where(prop => prop.PropertyType.Equals(memberType)))
{
var parameter = Expression.Parameter(destinationType);
var destinationMember = Expression.Lambda<Func<TDestination, TMember>>(Expression.Property(parameter, prop), parameter);
mapping.ForMember(destinationMember, opt);
}
return mapping;
}
Then you can configure the mapping as follows:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Fields, Contacts>().ForAllMembers<Fields, Contacts, Contact>(x => { x.Unflatten(); });
});

Unsupported Mapping Exception - Missing type map configuration in Automapper?

I'm sure I am missing something simple. First, I'll show all the code I have written to wire up the plumbing, then I'll show the exception message. Then, I'll set out what I have tried to fix it.
LicenceTrackerProfile
public class LicenceTrackerProfile : Profile
{
const string LicenceTrackerProfileName = "LicenceTrackerProfile";
public override string ProfileName
{
get { return LicenceTrackerProfileName; }
}
protected override void Configure()
{
// initialize mappings here
new ViewModelMappings(this).Initialize();
}
}
MapperBootstrapper
public class MapperBootstrapper
{
public void Configure()
{
var profile = new LicenceTrackerProfile();
AutoMapper.Mapper.Initialize(p => p.AddProfile(profile));
}
}
MappingBase
public abstract class MappingBase
{
private readonly Profile _profile;
protected MappingBase(Profile profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public Profile Profile
{
get { return _profile; }
}
}
UniversalMapper
public class UniversalMapper : IUniversalMapper
{
private readonly IMappingEngine _mappingEngine;
public UniversalMapper(IMappingEngine mappingEngine)
{
_mappingEngine = mappingEngine;
}
public virtual TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return _mappingEngine.Map(source, destination);
}
}
ViewModelMappings
public class ViewModelMappings : MappingBase, IMappingInitializer
{
private readonly Profile _profile;
public ViewModelMappings(Profile profile) : base(profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public void Initialize()
{
// data to domain mappings
Profile.CreateMap<EFDTO.Enums.FileTypes, Domain.FileTypes>();
Profile.CreateMap<EFDTO.Licence, Domain.Licence>();
Profile.CreateMap<EFDTO.LicenceAllocation, Domain.LicenceAllocation>();
Profile.CreateMap<EFDTO.Person, Domain.Person>();
Profile.CreateMap<EFDTO.Software, Domain.Software>();
Profile.CreateMap<EFDTO.SoftwareFile, Domain.SoftwareFile>();
Profile.CreateMap<EFDTO.SoftwareType, Domain.SoftwareType>();
}
}
Note, the initialize method and Configure method are being called, so they're not being "missed".
Exception
Missing type map configuration or unsupported mapping.
Mapping types: Software -> Software LicenceTracker.Entities.Software
-> LicenceTracker.DomainEntities.Software
Destination path: Software
Source value: LicenceTracker.Entities.Software
Troubleshooting
Ignoring columns. I planned to ignore columns, starting with all and then eliminating them by un-ignoring them 1 by 1 until I found the problem columns. However, to my surprise, the error occurs when I ignore all columns:
Profile.CreateMap<EFDTO.Software, Domain.Software>()
.ForMember(software => software.Licences, e => e.Ignore())
.ForMember(software => software.Name, e => e.Ignore())
.ForMember(software => software.SoftwareFiles, e => e.Ignore())
.ForMember(software => software.Type, e => e.Ignore())
.ForMember(software => software.Description, e => e.Ignore())
.ForMember(software => software.Id, e => e.Ignore())
.ForMember(software => software.TypeId, e => e.Ignore()
.ForMember(software => software.ObjectState, e => e.Ignore());
The Domain entities have [DataContract] (at class level) and [DataMember] (at method level) attributes. I added each of those attributes to the EF entities as well.
Other than that, I am out of ideas. It all seems to be wired up correctly.
What did I miss?
I'm back to heroically answer my question.
The problem was in the Service which created the UniversalMapper object (forgive the sloppy code, it is not final yet):
public class LicenceTrackerService : ILicenceTrackerService, IDisposable
{
LicenceTrackerContext context = new LicenceTrackerContext();
private MapperBootstrapper mapperBootstrapper;
private IUniversalMapper mapper = new UniversalMapper(Mapper.Engine);
private IUnitOfWork unitOfWork;
public LicenceTrackerService()
{
unitOfWork = new UnitOfWork(context, new RepositoryProvider(new RepositoryFactories()));
mapperBootstrapper = new MapperBootstrapper();
mapperBootstrapper.Configure();
Database.SetInitializer(new LicenceTrackerInitializer());
context.Database.Initialize(true);
}
public int GetNumber()
{
return 42;
}
public List<LicenceTracker.DomainEntities.Software> GetSoftwareProducts()
{
var productsRepo = unitOfWork.Repository<Software>();
var list = productsRepo.Query().Select().ToList();
var softwareList = new List<LicenceTracker.DomainEntities.Software>();
foreach (var software in list)
{
var softwareProduct = new LicenceTracker.DomainEntities.Software();
softwareList.Add(Mapper.Map(software, softwareProduct));
}
return softwareList;
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
I'm still not sure why, but initializing the mapper outside of the constructor (default value style) was not happy. By moving that instantiation into the constructor of the service, it worked:
private IUniversalMapper mapper;
public LicenceTrackerService()
{
mapper = new UniversalMapper(Mapper.Engine);
...
}
There's obviously something about static properties (Mapper.Engine) and default instantiations that I'm not understanding.
Anyway, no big deal as I was planning to inject the UniversalMapper into the service anyway.
Edit
I've actually figured out the problem for real now. It is an ordering thing. With Automapper, I had to initialize the mapper with the Profile before inserting the Mapper.Engine into the UniversalMapper.
Obviously, the Get aspect of the Mapper.Engine property is not just a memory reference to an object. And yes, a quick glance at the code inside Automapper confirms that.
So, assigning the result of the Get property to the _mappingEngine field of the UniversalMapper must happen after that engine has been configured.

Automapper - Convert NameValueCollection to Strongly Typed Collection

In automapper, how would I map a namevalue collection to a strongly typed collection?
Mapper.Map<NameValueCollection, List<MetaModel>>();
public class MetaModel
{
public string Name;
public string Value;
}
Piggybacking off of #dtryon's answer, the tough part about this is that there's no way to map the internal objects in NameValueCollection to your DTO type.
One thing you could do is write a custom converter that constructs KeyValuePair<string, string> objects from the items in the NameValueCollection. This would allow you to create a generic converter that leverages another mapping from KeyValuePair to a destination type of your choosing. Something like:
public class NameValueCollectionConverter<T> : ITypeConverter<NameValueCollection, List<T>>
{
public List<T> Convert(ResolutionContext ctx)
{
NameValueCollection source = ctx.SourceValue as NameValueCollection;
return source.Cast<string>()
.Select (v => MapKeyValuePair(new KeyValuePair<string, string>(v, source[v])))
.ToList();
}
private T MapKeyValuePair(KeyValuePair<string, string> source)
{
return Mapper.Map<KeyValuePair<string, string>, T>(source);
}
}
Then you would need to define a mapping from KeyValuePair<string, string> to MetaModel:
Mapper.CreateMap<KeyValuePair<string, string>, MetaModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key))
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value));
And finally, create a mapping between NameValueCollection and List<MetaModel>, using the custom converter:
Mapper.CreateMap<NameValueCollection, List<MetaModel>>()
.ConvertUsing<NameValueCollectionConverter<MetaModel>>();
Well, since NameValueCollection is so special, I don't think there is a good way to do this. This is mostly due to the fact that you can't get a handle on a key/value object inside the NameValueCollection. Luckily the code to map to the List<MetaModel> is not that bad. I would just map it manually and continue working:
[TestMethod]
public void TestMethod2()
{
List<MetaModel> dest = new List<MetaModel>();
NameValueCollection src = new NameValueCollection();
src.Add("Key1", "Value1");
src.Add("Key2", "Value2");
src.Add("Key3", "Value3");
src.Add("Key4", "Value4");
src.Add("Key5", "Value5");
foreach (var srcItem in src.AllKeys)
{
dest.Add(new MetaModel() { Name = srcItem, Value = src[srcItem] });
}
Assert.AreEqual(5, dest.Count);
}

Resources