[英]AutoMapper mapping unmapped properties to Dictionary / ExtensionData
我怎样才能使 AutoMapper 到 map 缺少未映射的属性到目标 object 内的字典? (就像序列化期间的ExtensionData )
例子:
class Source
{
public int A {get;set;}
public int B {get;set;}
public int C {get;set;}
}
class Destination
{
public int A {get;set;}
public Dictionary<string, object> D {get;set;}
}
Source s = new Source { A = 1, B = 2, C = 3 };
Destination d = ... // Mapping code
现在我想要以下结果:
d.A ==> 1
d.D ==> {{ "B", 2 }, { "C", 3 }}
* 编辑 *
最后我正在寻找一个没有反射的解决方案。 含义:在设置/配置/初始化过程中允许反射,但在映射过程中,我不希望反射造成任何延迟。
* 编辑 *
我正在寻找一个通用的解决方案,就像序列化程序一样。
您的问题有很多可能的解决方案。 我为您的属性创建了一个自定义值解析器,它完美地运行:
public class CustomResolver : IValueResolver<Source, Destination, Dictionary<string, object>>
{
public Dictionary<string, object> Resolve(Source source, Destination destination, Dictionary<string, object> destMember, ResolutionContext context)
{
destMember = new Dictionary<string, object>();
var flags = BindingFlags.Public | BindingFlags.Instance;
var sourceProperties = typeof(Source).GetProperties(flags);
foreach (var property in sourceProperties)
{
if (typeof(Destination).GetProperty(property.Name, flags) == null)
{
destMember.Add(property.Name, property.GetValue(source));
}
}
return destMember;
}
}
如何使用它?
static void Main(string[] args)
{
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.D, opt => opt.ResolveUsing<CustomResolver>());
});
var source = new Source { A = 1, B = 2, C = 3 };
var result = Mapper.Map<Source, Destination>(source);
}
public class Source
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
public class Destination
{
public int A { get; set; }
public Dictionary<string, object> D { get; set; }
}
我喜欢Pawel的解决方案,因为它更通用。 如果你想要更简单但更不通用的东西,你可以像这样初始化映射器:
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.D,
opt => opt.MapFrom(r => new Dictionary<string,object>(){{ "B", r.B},{ "C", r.C}}));
});
您可以结合 Pawel 的答案和 Reflection.Emit 来加快速度。 请注意,并非所有平台(如 iOS)都支持 Reflection.Emit。
与 ExtensionData 不同,这包括来自源的所有属性值。 我没有一个优雅的解决方案来确定哪些属性已经被映射,所以我只是提供了一种简单的方法来排除某些属性。
public class PropertyDictionaryResolver<TSource> : IValueResolver<TSource, object, Dictionary<string, object>>
{
private static readonly PropertyInfo[] Properties = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
private static readonly ConcurrentDictionary<PropertyInfo, Func<TSource, object>> GetterCache = new ConcurrentDictionary<PropertyInfo, Func<TSource, object>>();
public HashSet<MemberInfo> ExcludedProperties;
public PropertyDictionaryResolver()
{
ExcludedProperties = new HashSet<MemberInfo>();
}
public PropertyDictionaryResolver(Expression<Func<TSource, object>> excludeMembers)
{
var members = ExtractMembers(excludeMembers);
ExcludedProperties = new HashSet<MemberInfo>(members);
}
public Dictionary<string, object> Resolve(TSource source, object destination, Dictionary<string, object> existing, ResolutionContext context)
{
var destMember = new Dictionary<string, object>();
foreach (var property in Properties)
{
if (ExcludedProperties.Contains(property)) continue;
var exp = GetOrCreateExpression(property);
var value = exp(source);
if (value != null)
{
destMember.Add(property.Name, value);
}
}
return destMember;
}
/// <summary>
/// Creates and compiles a getter function for a property
/// </summary>
/// <param name="propInfo"></param>
/// <returns></returns>
public Func<TSource, object> GetOrCreateExpression(PropertyInfo propInfo)
{
if (GetterCache.TryGetValue(propInfo, out var existing))
{
return existing;
}
var parameter = Expression.Parameter(typeof(TSource));
var property = Expression.Property(parameter, propInfo);
var conversion = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<TSource, object>>(conversion, parameter);
existing = lambda.Compile();
GetterCache.TryAdd(propInfo, existing);
return existing;
}
/// <summary>
/// Pull the used MemberInfo out of a simple expression. Supports the following expression types only:
/// s => s.Prop1
/// s => new { s.Prop1, s.Prop2 }
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
public static IEnumerable<MemberInfo> ExtractMembers<T>(Expression<Func<T, object>> expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
switch (expression.Body)
{
case MemberExpression memberExpression:
yield return memberExpression.Member;
yield break;
// s => s.BarFromBaseType
case UnaryExpression convertExpression:
if (convertExpression.Operand is MemberExpression exp)
{
yield return exp.Member;
}
yield break;
// s => new { s.Foo, s.Bar }
case NewExpression newExpression:
if (newExpression.Arguments.Count == 0)
{
yield break;
}
foreach (var argument in newExpression.Arguments.OfType<MemberExpression>())
{
yield return argument.Member;
}
yield break;
}
throw new NotImplementedException("Unrecognized lambda expression.");
}
}
并像其中之一一样使用它
[TestClass]
public class Examples
{
[TestMethod]
public void AllProperties()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom<PropertyDictionaryResolver<Source>>());
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"A":1,"B":2,"C":3}}
}
[TestMethod]
public void ExcludeSingleProperty()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom(new PropertyDictionaryResolver<Source>(x => x.A)));
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"B":2,"C":3}}
}
[TestMethod]
public void ExcludeMultipleProperties()
{
var mapper = new Mapper(new MapperConfiguration(p =>
{
p.CreateMap<Source, Destination>()
.ForMember(x => x.A, cfg => cfg.MapFrom(x => x.A))
.ForMember(x => x.D, cfg => cfg.MapFrom(new PropertyDictionaryResolver<Source>(x => new
{
x.A,
x.B
})));
}));
var source = new Source { A = 1, B = 2, C = 3 };
var d = mapper.Map<Destination>(source);
// {"A":1,"D":{"C":3}}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.