Mercurial > pub > bltoolkit
view Source/Mapping/ExpressionMapper.cs @ 9:1e85f66cf767 default tip
update bltoolkit
author | nickolay |
---|---|
date | Thu, 05 Apr 2018 20:53:26 +0300 |
parents | f990fcb411a9 |
children |
line wrap: on
line source
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using BLToolkit.Common; using BLToolkit.Data.Linq; using BLToolkit.Reflection; using JetBrains.Annotations; namespace BLToolkit.Mapping { class Mapper<TS,TD> { public Func<TS,MappingContext,TD> Map; } class MappingContext { public Dictionary<object,object> Objects; public Func<object,object> GetParent; public List<Action<object>> CrossActions; public Dictionary<object,List<Action<object,object>>> Crosses; } class MappingParameters { public MappingSchema MappingSchema; public bool DeepCopy = true; public bool HandleCrossReferences = true; public bool IncludeComplexMapping; public Dictionary<object,object> MapperList = new Dictionary<object,object>(); public bool UseContext; public bool ContextParameterUsed; readonly ParameterExpression _mappingContext = Expression.Parameter(typeof(MappingContext), "ctx"); public ParameterExpression MappingContext { get { ContextParameterUsed = true; return _mappingContext; } } } public class ExpressionMapper<TSource,TDest> { readonly MappingParameters _parameters; private Func<object,object> _getCurrent; private Action<object,object> _setCurrent; public bool DeepCopy { get { return _parameters.DeepCopy; } set { _parameters.DeepCopy = value; } } public bool HandleBackReferences { get { return _parameters.HandleCrossReferences; } set { _parameters.HandleCrossReferences = value; } } public bool IncludeComplexMapping { get { return _parameters.IncludeComplexMapping; } set { _parameters.IncludeComplexMapping = value; } } public ExpressionMapper() : this(Map.DefaultSchema) { } public ExpressionMapper(MappingSchema mappingSchema) { _parameters = new MappingParameters { MappingSchema = mappingSchema }; } ExpressionMapper(MappingParameters parameters) { _parameters = parameters; } #region Value Converter interface IValueConvertHelper { Expression GetConverter (Expression source); Expression CheckNull (ExpressionMapper<TSource,TDest> mapper, Expression source, object nullValue, MapValue[] mapValues, object defaultValue, MapValue[] srcMapValues); Expression SourceMapValues(ExpressionMapper<TSource,TDest> mapper, Expression source, object nullValue, object defaultValue, MapValue[] srcMapValues); Expression DestMapValues (ExpressionMapper<TSource,TDest> mapper, Expression source, object nullValue, MapValue[] mapValues, object defaultValue); } class ValueConvertHelper<TS,TD> : IValueConvertHelper { public Expression GetConverter(Expression source) { return Expression.Invoke(Expression.Constant(Convert<TD,TS>.From), source); } public Expression CheckNull( ExpressionMapper<TSource,TDest> mapper, Expression source, object nullValue, MapValue[] mapValues, object defaultValue, MapValue[] srcMapValues) { var param = source.NodeType != ExpressionType.MemberAccess && source.NodeType != ExpressionType.Parameter && source.NodeType != ExpressionType.Constant? Expression.Parameter(typeof(TS), "p") : null; var nexpr = Expression.Constant(nullValue ?? default(TD)); var expr = source.NodeType == ExpressionType.Constant && ((ConstantExpression)source).Value == null ? nexpr as Expression: Expression.Condition( Expression.Equal(param ?? source, Expression.Constant(null)), nexpr.Value == null ? Expression.Convert(nexpr, typeof(TD)) : nexpr as Expression, mapper.GetValueMapper(param ?? source, typeof(TD), false, null, mapValues, defaultValue, srcMapValues)); return param == null ? expr : Expression.Invoke(Expression.Lambda<Func<TS,TD>>(expr, param), source); } public Expression SourceMapValues( ExpressionMapper<TSource,TDest> mapper, Expression source, object nullValue, object defaultValue, MapValue[] srcMapValues) { var param = //source.NodeType != ExpressionType.MemberAccess && source.NodeType != ExpressionType.Parameter && source.NodeType != ExpressionType.Constant? Expression.Parameter(typeof(TS), "p") : null; var expr = mapper.GetValueMapper(Expression.Constant(defaultValue), typeof(TD), true, nullValue, null, null, null); for (var i = srcMapValues.Length - 1; i >= 0; i--) { var value = srcMapValues[i]; expr = Expression.Condition( Expression.Equal(param ?? source, mapper.GetValueMapper(Expression.Constant(value.OrigValue), typeof(TS), false, null, null, null, null)), mapper.GetValueMapper(Expression.Constant(value.MapValues[0]), typeof(TD), true, nullValue, null, null, null), expr); } return param == null ? expr : Expression.Invoke(Expression.Lambda<Func<TS,TD>>(expr, param), source); } public Expression DestMapValues( ExpressionMapper<TSource,TDest> mapper, Expression source, object nullValue, MapValue[] mapValues, object defaultValue) { var param = //source.NodeType != ExpressionType.MemberAccess && source.NodeType != ExpressionType.Parameter && source.NodeType != ExpressionType.Constant? Expression.Parameter(typeof(TS), "p") : null; var expr = mapper.GetValueMapper(Expression.Constant(defaultValue), typeof(TD), true, nullValue, null, null, null); for (var i = mapValues.Length - 1; i >= 0; i--) { var value = mapValues[i]; var orex = null as Expression; foreach (var mapValue in value.MapValues) { var ex = Expression.Equal(param ?? source, mapper.GetValueMapper(Expression.Constant(mapValue), typeof (TS), false, null, null, null, null)); orex = orex == null ? ex : Expression.OrElse(orex, ex); } if (orex != null) expr = Expression.Condition( orex, mapper.GetValueMapper(Expression.Constant(value.OrigValue), typeof(TD), true, nullValue, null, null, null), expr); } return param == null ? expr : Expression.Invoke(Expression.Lambda<Func<TS,TD>>(expr, param), source); } } static IValueConvertHelper GetValueHelper(Type stype, Type dtype) { var type = typeof(ValueConvertHelper<,>).MakeGenericType(typeof(TSource), typeof(TDest), stype, dtype); return ((IValueConvertHelper)Activator.CreateInstance(type)); } #endregion #region Object Converter interface IConvertHelper { Expression MapObjects(ExpressionMapper<TSource,TDest> mapper, Expression source); Expression MapLists (ExpressionMapper<TSource,TDest> mapper, Expression source); } class ConvertHelper<TS,TD> : IConvertHelper where TS : class where TD : class { static TD MapCrossReferences( MappingContext ctx, TS source, Func<TS,TD> func, Func<object,object> getCurrent, Action<object,object> setCurrent) { if (source == null) return null; object dest; List<Action<object,object>> list; if (ctx.Objects.TryGetValue(source, out dest)) { if (dest == null) { if (ctx.Crosses == null) ctx.Crosses = new Dictionary<object,List<Action<object,object>>>(); if (!ctx.Crosses.TryGetValue(source, out list)) ctx.Crosses[source] = list = new List<Action<object,object>>(); var getParent = ctx.GetParent; Action<object,object> setter = (obj,value) => setCurrent(getParent(obj), value); list.Add(setter); } return (TD)dest; } var currParent = ctx.GetParent; ctx.GetParent = p => getCurrent(currParent(p)); ctx.Objects.Add(source, null); ctx.Objects[source] = dest = func(source); ctx.GetParent = currParent; if (ctx.Crosses != null && ctx.Crosses.TryGetValue(source, out list)) { if (ctx.CrossActions == null) ctx.CrossActions = new List<Action<object>>(); foreach (var action in list) { var setValue = action; Action<object> f = parent => setValue(parent, dest); ctx.CrossActions.Add(f); } ctx.Crosses.Remove(source); } return (TD)dest; } static TD MapObjects(TS source, Func<TS,TD> func) { return source == null ? null : func(source); } public Expression MapObjects(ExpressionMapper<TSource,TDest> mapper, Expression source) { var param = mapper._getCurrent == null ? (ParameterExpression)source : Expression.Parameter(source.Type, "source"); Expression expr; object m; if (mapper._parameters.MapperList.TryGetValue(new { S = typeof(TS), D = typeof(TD) }, out m)) { var map = (Mapper<TS,TD>)m; if (map.Map == null) { expr = Expression.Invoke( Expression.PropertyOrField(Expression.Constant(map), "Map"), source, mapper._parameters.MappingContext); } else { expr = Expression.Invoke(Expression.Constant(map.Map), source, mapper._parameters.MappingContext); } } else { var exmap = new ExpressionMapper<TS,TD>(mapper._parameters); expr = exmap.GetMemberInit(param); } if (mapper._getCurrent == null) return expr; if (!mapper.HandleBackReferences) { Expression<Func<object>> func = () => MapObjects((TS)null, null); return Expression.Call((MethodInfo)ReflectionHelper.MemeberInfo(func), source, Expression.Lambda<Func<TS,TD>>(expr, param)); } else { mapper._parameters.UseContext = true; Expression<Func<object>> func = () => MapCrossReferences(null, null, null, null, null); return Expression.Call( (MethodInfo)ReflectionHelper.MemeberInfo(func), mapper._parameters.MappingContext, source, Expression.Lambda<Func<TS,TD>>(expr, param), Expression.Constant(mapper._getCurrent), Expression.Constant(mapper._setCurrent)); } } interface IItemHelper { Expression MapLists(ExpressionMapper<TSource,TDest> mapper, Expression source); } interface IClassItemHelper { MethodInfo GetObjectArrayInfo(); MethodInfo GetObjectListInfo(bool isList); } class ClassItemHelper<TSourceItem,TDestItem> : IClassItemHelper where TSourceItem : class where TDestItem : class { static TDestItem[] MapObjectArray(MappingContext ctx, IEnumerable<TSourceItem> source, Func<TSourceItem,TDestItem> itemMapper) { if (source == null) return null; if (source is ICollection) { var col = (ICollection)source; var dest = new TDestItem[col.Count]; var n = 0; foreach (var item in source) { var current = n; dest[n++] = ConvertHelper<TSourceItem,TDestItem>.MapCrossReferences( ctx, item, itemMapper, _ => dest[current], (_,v) => { dest[current] = (TDestItem)v; }); } return dest; } else { TDestItem[] dest = null; var list = new List<TDestItem>(); var n = 0; foreach (var item in source) { var current = n; list.Add(null); list[n++] = ConvertHelper<TSourceItem,TDestItem>.MapCrossReferences( ctx, item, itemMapper, _ => dest == null ? list[current] : dest[current], (_,v) => { if (dest == null) list[current] = (TDestItem)v; else dest[current] = (TDestItem)v; }); } return dest = list.ToArray(); } } [UsedImplicitly] static TList MapObjectList<TList>(MappingContext ctx, IEnumerable<TSourceItem> source, Func<TSourceItem,TDestItem> itemMapper) where TList : class, IList<TDestItem>, new() { if (source == null) return null; var n = 0; var dest = source is ICollection && typeof(TList) == typeof(List<TDestItem>) ? (TList)(IList<TDestItem>)new List<TDestItem>(((ICollection)source).Count) : new TList(); foreach (var item in source) { var current = n; dest.Add(null); dest[n++] = ConvertHelper<TSourceItem,TDestItem>.MapCrossReferences( ctx, item, itemMapper, _ => dest[current], (_,v) => { dest[current] = (TDestItem)v; }); } return dest; } public MethodInfo GetObjectArrayInfo() { Expression<Func<object>> arrMapper = () => MapObjectArray(null, null, null); return (MethodInfo)ReflectionHelper.MemeberInfo(arrMapper); } public MethodInfo GetObjectListInfo(bool isList) { var method = typeof(ClassItemHelper<TSourceItem,TDestItem>).GetMethod("MapObjectList", BindingFlags.NonPublic | BindingFlags.Static); return method.MakeGenericMethod(isList ? typeof (List<TDestItem>) : typeof (TD)); } } class ItemHelper<TSourceItem,TDestItem> : IItemHelper { static TDestItem[] MapScalarArray(IEnumerable<TSourceItem> source, Func<TSourceItem,TDestItem> itemMapper) { if (source == null) return null; if (source is ICollection) { var col = (ICollection)source; var dest = new TDestItem[col.Count]; var n = 0; foreach (var item in source) dest[n++] = itemMapper(item); return dest; } return source.Select(itemMapper).ToArray(); } [UsedImplicitly] static TList MapScalarList<TList>(IEnumerable<TSourceItem> source, Func<TSourceItem,TDestItem> itemMapper) where TList : class, IList<TDestItem>, new() { if (source == null) return null; var dest = new TList(); var list = dest as List<TDestItem>; if (list != null) list.AddRange(source.Select(itemMapper)); else foreach (var item in source) dest.Add(itemMapper(item)); return dest; } public Expression MapLists(ExpressionMapper<TSource,TDest> mapper, Expression source) { var itemMapper = new ExpressionMapper<TSourceItem,TDestItem>(mapper._parameters); var itemParam = Expression.Parameter(typeof(TSourceItem), "item"); var itemExpr = itemMapper.GetValueMapper( itemParam, typeof(TDestItem), true, mapper._parameters.MappingSchema.GetNullValue (typeof(TDestItem)), mapper._parameters.MappingSchema.GetMapValues (typeof(TDestItem)), mapper._parameters.MappingSchema.GetDefaultValue(typeof(TDestItem)), mapper._parameters.MappingSchema.GetMapValues (typeof(TSourceItem))); var itemLambda = Expression.Lambda<Func<TSourceItem,TDestItem>>(itemExpr, itemParam); var isSourceScalar = !typeof(TSourceItem).IsArray && TypeHelper.IsScalar(typeof(TSourceItem)); var isDestScalar = !typeof(TDestItem). IsArray && TypeHelper.IsScalar(typeof(TDestItem)); if (!mapper.HandleBackReferences || isSourceScalar || isDestScalar) { if (typeof (TD).IsArray) { Expression<Func<object>> arrMapper = () => MapScalarArray(null, null); return Expression.Call((MethodInfo)ReflectionHelper.MemeberInfo(arrMapper), source, itemLambda); } var isList = typeof (TD) == typeof (IEnumerable<TDestItem>) || typeof (TD) == typeof (ICollection<TDestItem>) || typeof (TD) == typeof (IList<TDestItem>) || typeof (TD) == typeof (List<TDestItem>); var method = typeof (ItemHelper<TSourceItem, TDestItem>).GetMethod("MapScalarList", BindingFlags.NonPublic | BindingFlags.Static); method = method.MakeGenericMethod(isList ? typeof (List<TDestItem>) : typeof (TD)); return Expression.Call(method, source, itemLambda); } else { mapper._parameters.UseContext = true; var type = typeof (ClassItemHelper<,>).MakeGenericType( typeof (TSource), typeof (TDest), typeof (TS), typeof (TD), typeof (TSourceItem), typeof (TDestItem)); var helper = ((IClassItemHelper)Activator.CreateInstance(type)); if (typeof (TD).IsArray) return Expression.Call(helper.GetObjectArrayInfo(), mapper._parameters.MappingContext, source, itemLambda); var isList = typeof (TD) == typeof (IEnumerable<TDestItem>) || typeof (TD) == typeof (ICollection<TDestItem>) || typeof (TD) == typeof (IList<TDestItem>) || typeof (TD) == typeof (List<TDestItem>); return Expression.Call(helper.GetObjectListInfo(isList), mapper._parameters.MappingContext, source, itemLambda); } } } public Expression MapLists(ExpressionMapper<TSource,TDest> mapper, Expression source) { var ts = TypeHelper.GetGenericType(typeof(IEnumerable<>), typeof(TS)).GetGenericArguments()[0]; var td = TypeHelper.GetGenericType(typeof(IEnumerable<>), typeof(TD)).GetGenericArguments()[0]; var type = typeof(ItemHelper<,>).MakeGenericType(typeof(TSource), typeof(TDest), typeof(TS), typeof(TD), ts, td); return ((IItemHelper)Activator.CreateInstance(type)).MapLists(mapper, source); } } static IConvertHelper GetHelper(Type stype, Type dtype) { var type = typeof(ConvertHelper<,>).MakeGenericType(typeof(TSource), typeof(TDest), stype, dtype); return ((IConvertHelper)Activator.CreateInstance(type)); } #endregion Expression GetValueMapper( Expression source, Type dtype, bool checkNull, object nullValue, MapValue[] destMapValues, object defaultValue, MapValue[] srcMapValues) { var stype = source.Type; var isSourceScalar = !stype.IsArray && TypeHelper.IsScalar(stype); var isDestScalar = !dtype.IsArray && TypeHelper.IsScalar(dtype); if (dtype == typeof(object) || dtype == stype && (!DeepCopy || isSourceScalar)) return source; var isSourceNullable = TypeHelper.IsNullableType(stype) || stype.IsClass; if (checkNull && isSourceNullable && !TypeHelper.IsNullableType(dtype) && (isDestScalar || isSourceScalar)) return GetValueHelper(stype, dtype).CheckNull(this, source, nullValue, destMapValues, defaultValue, srcMapValues); if (srcMapValues != null) return GetValueHelper(stype, dtype).SourceMapValues(this, source, nullValue, defaultValue, srcMapValues); if (destMapValues != null) return GetValueHelper(stype, dtype).DestMapValues(this, source, nullValue, destMapValues, defaultValue); if (dtype == typeof (string)) return isSourceNullable ? Expression.Condition( Expression.Equal(source, Expression.Constant(null)), Expression.Constant(null), Expression.Call(source, "ToString", Array<Type>.Empty)) as Expression : Expression.Call(source, "ToString", Array<Type>.Empty); if (!isDestScalar && !isSourceScalar) { if (TypeHelper.GetGenericType(typeof(IEnumerable<>), dtype) != null && TypeHelper.GetGenericType(typeof(IEnumerable<>), stype) != null) return GetHelper(stype, dtype).MapLists(this, source); return GetHelper(stype, dtype).MapObjects(this, source); } try { return Expression.Convert(source, dtype); } catch (InvalidOperationException) { } return GetValueHelper(stype, dtype).GetConverter(source); } IEnumerable<MemberBinding> GetBindings(Expression source) { var dest = _parameters.MappingSchema.GetObjectMapper(typeof(TDest)); var src = _parameters.MappingSchema.GetObjectMapper(typeof(TSource)); foreach (MemberMapper dmm in dest) { if (!IncludeComplexMapping && dmm is MemberMapper.ComplexMapper) continue; var dma = dmm.MemberAccessor; if (!dma.HasSetter) continue; var attr = dma.GetAttribute<ExpressionMapIgnoreAttribute>(); if (attr != null && attr.Ignore) continue; var smm = src[dmm.Name]; if (smm == null) continue; if (!IncludeComplexMapping && smm is MemberMapper.ComplexMapper) continue; var sma = smm.MemberAccessor; if (!sma.HasGetter) continue; attr = sma.GetAttribute<ExpressionMapIgnoreAttribute>(); if (attr != null && attr.Ignore) continue; _getCurrent = dma.GetValue; _setCurrent = dma.SetValue; var bind = Expression.Bind( dma.MemberInfo, GetValueMapper( Expression.PropertyOrField(source, sma.Name), dma.Type, true, dmm.MapMemberInfo.NullValue, dmm.MapMemberInfo.MapValues, dmm.MapMemberInfo.DefaultValue, smm.MapMemberInfo.MapValues)); yield return bind; } var destMembers = from m in ((IEnumerable<MemberAccessor>)dest.TypeAccessor) select m; destMembers = destMembers.Except(dest.Select(mm => mm.MemberAccessor)).ToList(); var srcMembers = (from m in ((IEnumerable<MemberAccessor>)src.TypeAccessor) select m) .Except(src.Select(mm => mm.MemberAccessor)) .ToList(); foreach (var dma in destMembers) { if (!dma.HasSetter) continue; var sma = srcMembers.FirstOrDefault(mi => mi.Name == dma.Name); if (sma == null || !sma.HasGetter) continue; _getCurrent = dma.GetValue; _setCurrent = dma.SetValue; var bind = Expression.Bind( dma.MemberInfo, GetValueMapper( Expression.MakeMemberAccess(source, sma.MemberInfo), dma.Type, true, _parameters.MappingSchema.GetNullValue (dma.Type), _parameters.MappingSchema.GetMapValues (dma.Type), _parameters.MappingSchema.GetDefaultValue(dma.Type), _parameters.MappingSchema.GetMapValues (sma.Type))); yield return bind; } } Expression GetMemberInit(ParameterExpression source) { var mapper = new Mapper<TSource,TDest>(); _parameters.MapperList.Add(new { S = typeof(TSource), D = typeof(TDest) }, mapper); var dest = TypeAccessor<TDest>.Instance; var expr = Expression.MemberInit(Expression.New(dest.Type), GetBindings(source)); mapper.Map = Expression.Lambda<Func<TSource,MappingContext,TDest>>(expr, source, _parameters.MappingContext).Compile(); return expr; } interface IAbstractHelper { Func<TSource,TDest> GetMapper(MappingParameters ps); } class AbstractHelper<TS,TD> : IAbstractHelper { public Func<TSource,TDest> GetMapper(MappingParameters ps) { var em = new ExpressionMapper<TS,TD>(ps); var mapper = em.GetMapper(); return source => (TDest)(object)mapper((TS)(object)source); } } public Func<TSource,TDest> GetMapper() { if (typeof(TSource) == typeof(TDest) && !DeepCopy) return s => (TDest)(object)s; if (TypeHelper.IsAbstractClass(typeof(TSource)) || TypeHelper.IsAbstractClass(typeof(TDest))) { var st = TypeHelper.IsAbstractClass(typeof(TSource)) ? TypeAccessor<TSource>.Instance.Type : typeof(TSource); var dt = TypeHelper.IsAbstractClass(typeof(TDest)) ? TypeAccessor<TDest>. Instance.Type : typeof(TDest); var type = typeof(AbstractHelper<,>).MakeGenericType(typeof(TSource), typeof(TDest), st, dt); return ((IAbstractHelper)Activator.CreateInstance(type)).GetMapper(_parameters); } var parm = Expression.Parameter(typeof(TSource), "src"); var expr = GetValueMapper( parm, typeof(TDest), true, _parameters.MappingSchema.GetNullValue (typeof(TDest)), _parameters.MappingSchema.GetMapValues (typeof(TDest)), _parameters.MappingSchema.GetDefaultValue(typeof(TDest)), _parameters.MappingSchema.GetMapValues (typeof(TSource))); if (_parameters.ContextParameterUsed) { var l = Expression.Lambda<Func<TSource,MappingContext,TDest>>(expr, parm, _parameters.MappingContext); var f = l.Compile(); if (!_parameters.UseContext) return s => f(s, null); return s => { var ctx = new MappingContext { Objects = new Dictionary<object,object>(10) { { s, null } }, GetParent = p => p, }; var dest = f(s, ctx); if (ctx.CrossActions != null) foreach (var circle in ctx.CrossActions) circle(dest); if (ctx.Crosses != null) { List<Action<object,object>> list; if (ctx.Crosses.TryGetValue(s, out list)) foreach (var action in list) action(dest, dest); } return dest; }; } var lambda = Expression.Lambda<Func<TSource,TDest>>(expr, parm); return lambda.Compile(); } } }