Automapper easily handles mapping one list of object types to another list of different objects types, but is it possible to have it map to an existing list using an ID as a key?
I have not found better way than the following.
Here are source and destination.
public class Source
{
public int Id { get; set; }
public string Foo { get; set; }
}
public class Destination
{
public int Id { get; set; }
public string Foo { get; set; }
}
Define converter (You should change List<> to whatever type you are using).
public class CollectionConverter: ITypeConverter<List<Source>, List<Destination>>
{
public List<Destination> Convert(ResolutionContext context)
{
var destinationCollection = (List<Destination>)context.DestinationValue;
if(destinationCollection == null)
destinationCollection = new List<Destination>();
var sourceCollection = (List<Source>)context.SourceValue;
foreach(var source in sourceCollection)
{
Destination matchedDestination = null;
foreach(var destination in destinationCollection)
{
if(destination.Id == source.Id)
{
Mapper.Map(source, destination);
matchedDestination = destination;
break;
}
}
if(matchedDestination == null)
destinationCollection.Add(Mapper.Map<Destination>(source));
}
return destinationCollection;
}
}
And here is actual mapping configuration and example.
Mapper.CreateMap<Source,Destination>();
Mapper.CreateMap<List<Source>,List<Destination>>().ConvertUsing(new CollectionConverter());
var sourceCollection = new List<Source>
{
new Source{ Id = 1, Foo = "Match"},
new Source{ Id = 2, Foo = "DoesNotMatchWithDestination"}
};
var destinationCollection = new List<Destination>
{
new Destination{ Id = 1, Foo = "Match"},
new Destination{ Id = 3, Foo = "DoeNotMatchWithSource"}
};
var mergedCollection = Mapper.Map(sourceCollection, destinationCollection);
You should get the following result.
I found this article very useful and as such I thought I would feed back in my generic version of the type converter which you can use to select the property to match on from each object.
Using it all you need to do is:
// Example of usage
Mapper.CreateMap<UserModel, User>();
var converter = CollectionConverterWithIdentityMatching<UserModel, User>.Instance(model => model.Id, user => user.Id);
Mapper.CreateMap<List<UserModel>, List<User>>().ConvertUsing(converter);
//The actual converter
public class CollectionConverterWithIdentityMatching<TSource, TDestination> :
ITypeConverter<List<TSource>, List<TDestination>> where TDestination : class
{
private readonly Func<TSource, object> sourcePrimaryKeyExpression;
private readonly Func<TDestination, object> destinationPrimaryKeyExpression;
private CollectionConverterWithIdentityMatching(Expression<Func<TSource, object>> sourcePrimaryKey, Expression<Func<TDestination, object>> destinationPrimaryKey)
{
this.sourcePrimaryKeyExpression = sourcePrimaryKey.Compile();
this.destinationPrimaryKeyExpression = destinationPrimaryKey.Compile();
}
public static CollectionConverterWithIdentityMatching<TSource, TDestination>
Instance(Expression<Func<TSource, object>> sourcePrimaryKey, Expression<Func<TDestination, object>> destinationPrimaryKey)
{
return new CollectionConverterWithIdentityMatching<TSource, TDestination>(
sourcePrimaryKey, destinationPrimaryKey);
}
public List<TDestination> Convert(ResolutionContext context)
{
var destinationCollection = (List<TDestination>)context.DestinationValue ?? new List<TDestination>();
var sourceCollection = (List<TSource>)context.SourceValue;
foreach (var source in sourceCollection)
{
TDestination matchedDestination = default(TDestination);
foreach (var destination in destinationCollection)
{
var sourcePrimaryKey = GetPrimaryKey(source, this.sourcePrimaryKeyExpression);
var destinationPrimaryKey = GetPrimaryKey(destination, this.destinationPrimaryKeyExpression);
if (string.Equals(sourcePrimaryKey, destinationPrimaryKey, StringComparison.OrdinalIgnoreCase))
{
Mapper.Map(source, destination);
matchedDestination = destination;
break;
}
}
if (matchedDestination == null)
{
destinationCollection.Add(Mapper.Map<TDestination>(source));
}
}
return destinationCollection;
}
private string GetPrimaryKey<TObject>(object entity, Func<TObject, object> expression)
{
var tempId = expression.Invoke((TObject)entity);
var id = System.Convert.ToString(tempId);
return id;
}
}
Related
I would like to use AutoMapper and provide some values in runtime.
For example I have DTO & ViewModel.
One of the property doesn't exist in DTO and cannot be mapped directly by using Converters/Resolvers/Transformers;
namespace Lab.So.Sample
{
public class UserDto
{
public int UserId { get; set; }
public string UserCode { get; set; }
}
public class UserGroupDto
{
public List<UserDto> Users { get; set; }
}
public class UserViewModel
{
public int UserId { get; set; }
public string FullName { get; set; }
}
public class UserGroup
{
public List<UserViewModel> Users { get; set; }
}
class Program
{
static void Main(string[] args)
{
var src = new Fixture().Create<UserGroupDto>();
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.ValidateInlineMaps = false;
});
// How to hook mapping of nested object User->UserViewModel to provide value of FullName in runtime
var dst = Mapper.Map<UserGroupDto, UserGroup>(src);
}
}
}
I have some workaround how do this, but it's not human-friendly, as for me:
class Program
{
internal class UserViewModelFullNameResolver : IValueResolver<UserDto, UserViewModel, string>
{
public string Resolve(UserDto source, UserViewModel destination, string destMember, ResolutionContext context)
{
var names = context.Items["ctx.Names"] as IDictionary<int, string>;
if (names == null || !names.TryGetValue(source.UserId, out var fullName))
{
return null;
}
return fullName;
}
}
static void Main(string[] args)
{
var src = new Fixture().Create<UserGroupDto>();
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.ValidateInlineMaps = false;
cfg.CreateMap<UserDto, UserViewModel>()
.ForMember(d => d.FullName, opt => opt.MapFrom<UserViewModelFullNameResolver>());
});
var names = new Dictionary<int, string>
{
{ 10, "FullName-10" },
{ 20, "FullName-20" },
};
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Items["ctx.Names"] = names);
}
}
In that workaround most inconvenient it's agreement of key names in opt.Items;
If something accidentally made typo it hard to investigate and fix;
I looking something like this:
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Use(new UserViewModelFullNameResolver());
By other words, its defining an instance of resolver in runtime for each unique case;
Also I would accept, if I will have ability to define a hook to mapping particular type in graph of objects:
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Hook<UserDto,UserViewModel>((s,d)=> { /* any logic to read external data */ });
An example of usage:
var srcA = readDataA();
var srcB = readDataB();
var dst = Mapper.Map<UserGroupDto, UserGroup>(
src,
opt=>opt.Hook<UserDto,UserViewModel>(
(s,d)=>
{
d.FullName = srcA + srcB;
});
Please suggest something, that would help to read data to accomplish mapping in cases when source doesn't have all required data for nested object in destination.
I was able to use existing API to archive my goal:
public static class InlineMappingExtensions
{
public static void EnableInlineMapping(this IMapperConfigurationExpression cfg)
{
cfg.ForAllMaps((t, i) =>
{
i.AfterMap((s, d, ctx) =>
{
ctx.ApplyInlineMap(s, d);
}
);
});
}
public static IMappingOperationOptions OnMap<TSrc, TDst>(this IMappingOperationOptions opts,
Action<TSrc, TDst> resolve)
{
var srcTypeName = typeof(TSrc);
var dstTypeName = typeof(TDst);
var ctxKey = $"OnMap_{srcTypeName}_{dstTypeName}";
opts.Items.Add(ctxKey, resolve);
return opts;
}
private static void ApplyInlineMap(this ResolutionContext opts, object src, object dst)
{
if (src == null)
{
return;
}
if (dst == null)
{
return;
}
var srcTypeName = src.GetType();
var dstTypeName = dst.GetType();
var ctxKey = $"OnMap_{srcTypeName}_{dstTypeName}";
if (!opts.Items.TryGetValue(ctxKey, out var inlineMap))
{
return;
}
var act = inlineMap as Delegate;
act?.DynamicInvoke(src, dst);
}
}
To enable it:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.ValidateInlineMaps = false;
cfg.EnableInlineMapping();
});
Sample of usage:
// read data from external sources
var names = new Dictionary<int, string>
{
{ 10, "FullName-10" },
{ 20, "FullName-20" },
};
Action<UserDto, UserViewModel> mapA = (s, d) =>
{
if (names.TryGetValue(s.UserId, out var name))
{
d.FullName = name;
}
};
Action<UserGroupDto, UserGroup> mapB = (s, d) =>
{
if (DateTime.Now.Ticks > 0)
{
d.Users = null;
}
};
var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt => opt.OnMap(mapA).OnMap(mapB));
I want to create generic expression filtering in IQueryable
public class Vehicle
{
public int Id { get; set; }
public string VehicleNO { get; set; }
public int DriverId { get; set; }
public Driver Driver {get;set;}
}
public class Driver
{
public int Id { get; set; }
public string Name { get; set; }
}
operator = "Contain", field name = "Driver.Name", Value filter =
"Micheal"
I don't know how to filter driver name.
Here is my full code
IQueryable<SysClientSiteUser> query = entity.SysClientSiteUsers.Include(i => i.SysClientSiteRole);
Dictionary<string, string> dtFilter = new Dictionary<string, string>();
dtFilter.Add("VehicleNo", "A123");
Dictionary<Type, Func<string, object>> lookup = new Dictionary<Type, Func<string, object>>();
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); });
var paramExpr = Expression.Parameter(typeof(Vehicle), "VehicleNo");
var keyPropExpr = Expression.Property(paramExpr, "VehicleNo");
if (!lookup.ContainsKey(keyPropExpr.Type))
throw new Exception("Unknown type : " + keyPropExpr.Type.ToString());
var typeDelegate = lookup[keyPropExpr.Type];
var constantExp = typeDelegate("A123");
var eqExpr = Expression.Equal(keyPropExpr, Expression.Constant(constantExp));
var condExpr = Expression.Lambda<Func<SysClientSiteUser, bool>>(eqExpr, paramExpr);
query = query.Where(condExpr);
for normal field, it's working. But if I want to call Driver name. it's not work. How to call "Driver.Name"?
You can use a helper function to convert a nested property name string to an Expression that accesses that property for a given ParameterExpression and type:
private static Expression MakePropertyExpression<T>(string propertyName, Expression baseExpr) =>
propertyName.Split('.').Aggregate(baseExpr, (b, pname) => Expression.Property(b, pname));
I have a complex domain object structure:
public class CustomerDomainObj
{
public CUSTOMER customer {get;set;}
public ORDER order {get;set;}
public PRODUCT product {get;set;}
}
DTO:
public class CustomerDTO
{
public string cust_name {get;set;}
public string price {get;set;}
public string description {get;set;}
}
I need to map properties of CUSTOMER,PRODUCT and ORDER objects to CustomerDTO
For that I have created an extension method:
public static TDestination Map<TSource, TDestination>(this TDestination destination, TSource source, IMapper mapper)
{
return mapper.Map(source, destination);
}
Configuration:
m.CreateMap<CUSTOMER, CustomerDTO>().ForMember(/*Some Code*/);
m.CreateMap<PRODUCT, CustomerDTO>().ForMember(/*Some Code*/);
m.CreateMap<ORDER, CustomerDTO>().ForMember(/*Some Code*/);
Usage:
var response = _mapper.Map<CustomerDTO>(repoCust.custObj)
.Map(repoCust.prodObj, _mapper);
.Map(repoCust.orderObj, _mapper);
Everything works fine!
Question:
I am looking for a way to skip passing the _mapper instance every time when I try to do some mapping.
Something like :
var response = _mapper.Map<CustomerDTO>(repoCust.custObj)
.Map(repoCust.prodObj);
.Map(repoCust.orderObj);
maybe you are looking for an approach like this...
using AutoMapper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace sandcastle1
{
class Program
{
static void Main(string[] args)
{
//we create a mapper instance
var _mapper = new MapperConfiguration(m =>
{
m.CreateMap<A, ADto>().ForMember(x => x.Y, opt => opt.MapFrom(x => x.X));
m.CreateMap<B, BDto>().ForMember(x => x.bar, opt => opt.MapFrom(x => x.foo));
}).CreateMapper();
//some dummy POCOs that we can map
var a1 = new A { X = 1 };
var a2 = new A { X = 2 };
var a3 = new A { X = 3 };
var b1 = new B { foo = 1 };
var b2 = new B { foo = 2 };
var b3 = new B { foo = 3 };
//mapping objects with fluent syntax:
// -- we do not repeat the mapper as a written parameter
// -- we do not specify the destination type but assume that there is exactly one TypeMap for the source type
var result = _mapper
.Map(a1)
.Map(a2)
.Map(a3)
.Map(b1)
.Map(b2)
.Map(b3);
//presenting the mapping result
var cnt = 1;
foreach (var dto in result)
{
var propinfo = dto.GetType().GetProperties().First();
Console.WriteLine($"Mapped object {cnt++} is of Type {dto.GetType().Name} with {propinfo.Name} = {propinfo.GetValue(dto)}");
}
}
}
//our dummy classes for the mapping
public class A
{
public int X { get; set; }
}
public class ADto
{
public int Y { get; set; }
}
public class B
{
public int foo { get; set; }
}
public class BDto
{
public int bar { get; set; }
}
public static class ExtensionMethodClass
{
//the first extension method ...
//takes the IMapper and maps the first object
//hands down all possible type mappings or throws an exception when we do not have exactly one mapping for the source type
public static MyFluentResult Map(this IMapper _mapper, object source)
{
//build a dictionary that gives us access to information about existing TypeMaps in the Mapper
//(we only want to know from wicht source type to which destination type)
var maps = _mapper.ConfigurationProvider.GetAllTypeMaps().GroupBy(x => x.SourceType).Where(x => x.Count() == 1).ToDictionary(x => x.Key, x => x.First().DestinationType);
Type stype = source.GetType();
Type dtype;
if (!maps.TryGetValue(stype, out dtype))
{
throw new Exception($"No suitable single mapping found for {stype.Name}");
}
//the magic happens here, we aggregate all necessary information for the following steps in this object
return new MyFluentResult
{
Mapper = _mapper,
Maps = maps,
ResultsSoFar = new object[] { _mapper.Map(source, stype, dtype) }
};
}
//this method is called for the second and all subsequent mapping operations
public static MyFluentResult Map(this MyFluentResult _fluentResult, object source)
{
//same as above, but we already have the dictionary...
Type stype = source.GetType();
Type dtype;
if (!_fluentResult.Maps.TryGetValue(stype, out dtype))
{
throw new Exception($"No suitable single mapping found for {stype.Name}");
}
//again we hand down the result and all other information for the next fluent call
return new MyFluentResult
{
Mapper = _fluentResult.Mapper,
Maps = _fluentResult.Maps,
//we can simply concat the results here
ResultsSoFar = _fluentResult.ResultsSoFar.Concat(new object[] { _fluentResult.Mapper.Map(source, stype, dtype) })
};
}
//omitted implementation for IEnumerable sources... but that would look pretty much the same
}
//the class that holds the aggregated data.... the mapper... the possible typemaps ... and the result data...
//wrapped IEnumerable of the results for convinience
public class MyFluentResult : IEnumerable<object>
{
public IMapper Mapper { get; set; }
public IEnumerable<object> ResultsSoFar { get; set; }
public Dictionary<Type, Type> Maps { get; set; }
public IEnumerator<object> GetEnumerator()
{
return ResultsSoFar.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ResultsSoFar.GetEnumerator();
}
}
}
Currently, am adding the properties and values to the object manually like this example and sending to Dapper.SimpleCRUD to fetch data from Dapper Orm. This is the desired output I would like to achieve.
object whereCriteria = null;
whereCriteria = new
{
CountryId = 2,
CountryName = "Anywhere on Earth",
CountryCode = "AOE",
IsActive = true
};
The following class should build the object in the above mentioned format and return the ready-made object.
public static class WhereClauseBuilder
{
public static object BuildWhereClause(object model)
{
object whereObject = null;
var properties = GetProperties(model);
foreach (var property in properties)
{
var value = GetValue(property, model);
//Want to whereObject according to the property and value. Need help in this part!!!
}
return whereObject;
}
private static object GetValue(PropertyInfo property, object model)
{
return property.GetValue(model);
}
private static IEnumerable<PropertyInfo> GetProperties(object model)
{
return model.GetType().GetProperties();
}
}
This function WhereClauseBuilder.BuildWhereClause(object model) should return the object in expected format (mentiond above). Here is the implementation of how I would like to use.
public sealed class CountryModel
{
public int CountryId { get; set; }
public string CountryName { get; set; }
public string CountryCode { get; set; }
public bool IsActive { get; set; }
}
public class WhereClauseClass
{
public WhereClauseClass()
{
var model = new CountryModel()
{
CountryCode = "AOE",
CountryId = 2,
CountryName = "Anywhere on Earth",
IsActive = true
};
//Currently, won't return the correct object because the implementation is missing.
var whereClauseObject = WhereClauseBuilder.BuildWhereClause(model);
}
}
Maybe something like that:
private const string CodeTemplate = #"
namespace XXXX
{
public class Surrogate
{
##code##
}
}";
public static Type CreateSurrogate(IEnumerable<PropertyInfo> properties)
{
var compiler = new CSharpCodeProvider();
var compilerParameters = new CompilerParameters { GenerateInMemory = true };
foreach (var item in AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic))
{
compilerParameters.ReferencedAssemblies.Add(item.Location);
}
var propertiesCode =
string.join("\n\n", from pi in properties
select "public " + pi.PropertyType.Name + " " + pi.Name + " { get; set; }");
var source = CodeTemplate.Replace("##code##", propertiesCode);
var compilerResult = compiler.CompileAssemblyFromSource(compilerParameters, source);
if (compilerResult.Errors.HasErrors)
{
throw new InvalidOperationException(string.Format("Surrogate compilation error: {0}", string.Join("\n", compilerResult.Errors.Cast<CompilerError>())));
}
return compilerResult.CompiledAssembly.GetTypes().First(x => x.Name == "Surrogate");
}
And now use it:
public static object BuildWhereClause(object model)
{
var properties = GetProperties(model);
var surrogateType = CreateSurrogate(properties);
var result = Activator.CreateInstance(surrogateType);
foreach (var property in properties)
{
var value = GetValue(property, model);
var targetProperty = surrogateType.GetProperty(property.Name);
targetProperty.SetValue(result, value, null);
}
return result;
}
I didn't compile that. It's only written here. Maybe there are some errors. :-)
EDIT:
To use ExpandoObject you can try this:
public static object BuildWhereClause(object model)
{
var properties = GetProperties(model);
var result = (IDictionary<string, object>)new ExpandoObject();
foreach (var property in properties)
{
var value = GetValue(property, model);
result.Add(property.Name, value);
}
return result;
}
But I don't know whether this will work for you.
I have created a method which will create dynamic object list from an object list according to property list. In this case I have completed such task using Expandoobject. But I have failed to create distinct list of such expando object list. Please visit the following fidder and see my code.
public class Program
{
public static void Main()
{
var _dynamicObjectList = new List<Student>();
for (int i = 0; i < 5; i++)
{
_dynamicObjectList.Add(new Student { ID = i, Name = "stu" + i, Address = "address" + i, AdmissionDate = DateTime.Now.AddDays(i) , Age=15, FatherName="Jamal"+i, MotherName = "Jamila"+i});
}
//create again for checking distinct list
for (int i = 0; i < 5; i++)
{
_dynamicObjectList.Add(new Student { ID = i, Name = "stu" + i, Address = "address" + i, AdmissionDate = DateTime.Now.AddDays(i), Age = 15, FatherName = "Jamal" + i, MotherName = "Jamila" + i });
}
// var returnList = test2.GetDdlData<Object>(_dynamicObjectList, "ID,Name,Address");
// var returnList = test2.GetDdlData<Object>(_dynamicObjectList, "ID,FatherName,Address");
var returnList = test2.GetDdlData<Object>(_dynamicObjectList, "ID,Name,FatherName,MotherName,Age");
string strSerializeData = JsonConvert.SerializeObject(returnList);
Console.WriteLine(strSerializeData);
Console.ReadLine();
}
}
public class Student
{
public int? ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime AdmissionDate { get; set; }
public string FatherName { get; set; }
public string MotherName { get; set; }
public int Age { get; set; }
}
public static class test2
{
public static IList GetDdlData<T>(this IEnumerable<T> source, string userParams)
{
try
{
List<string> otherProperties = userParams.Split(',').ToList();
Dictionary<string, PropertyInfo> parentPropertyInfo = new Dictionary<string, PropertyInfo>();
Dictionary<string, Type> parentType = new Dictionary<string, Type>();
var dynamicObjectList = (from k in source select k).ToList();
if (dynamicObjectList.Count() > 0)
{
//if parentField exists then system will handle parent property
if (otherProperties.Count > 0)
{
foreach (string otherProperty in otherProperties)
{
//get parent field property info
PropertyInfo _info = dynamicObjectList[0].GetType().GetProperty(otherProperty);
parentPropertyInfo.Add(otherProperty, _info);
//get parent field propertyType
Type pType = Nullable.GetUnderlyingType(_info.PropertyType) ?? _info.PropertyType;
parentType.Add(otherProperty, pType);
}
}
}
//return List
IList<object> objList = new List<object>();
foreach (T obj in source)
{
var dynamicObj = new ExpandoObject() as IDictionary<string, Object>;
foreach (string otherProperty in otherProperties)
{
PropertyInfo objPropertyInfo = parentPropertyInfo.FirstOrDefault(m => m.Key == otherProperty).Value;
Type objPropertyType = parentType.FirstOrDefault(m => m.Key == otherProperty).Value;
Object data = (objPropertyInfo.GetValue(obj, null) == null) ? null : Convert.ChangeType(objPropertyInfo.GetValue(obj, null), objPropertyType, null);
dynamicObj.Add(otherProperty, data);
}
objList.Add(dynamicObj);
}
var returnUniqList = objList.Distinct().ToList();
return returnUniqList;
}
catch (Exception ex)
{
throw ex;
}
}
}
https://dotnetfiddle.net/hCuJwD
Just add the following code in the code block.
foreach(var objTemp in objList) {
bool isNotSimilar = true;
foreach(string property in otherProperties) {
//get sending object property data
object tempFValue = (objTemp as IDictionary < string, Object > )[property];
//get current object property data
object tempCValue = (dynamicObj as IDictionary < string, Object > )[property];
if (!tempFValue.Equals(tempCValue)) {
isNotSimilar = false;
break;
}
}
if (isNotSimilar) {
isDuplicate = true;
break;
}
}
DOTNETFIDDLE