Skip to content
Browse files

Enforcing max depth when building expression tree; closes #465

  • Loading branch information...
1 parent 0d83f85 commit 1c3db4e73aa07460ed7ad2c18437718ff5d2812e @jbogard jbogard committed
View
31 src/AutoMapper/Internal/MappingExpression.cs
@@ -303,9 +303,9 @@ public void Condition(Func<TSource, bool> condition)
Condition(context => condition((TSource)context.Parent.SourceValue));
}
- public IMappingExpression<TSource, TDestination> MaxDepth(int depth)
- {
- _typeMap.SetCondition(o => PassesDepthCheck(o, depth));
+ public IMappingExpression<TSource, TDestination> MaxDepth(int depth)
+ {
+ _typeMap.MaxDepth = depth;
return this;
}
@@ -343,31 +343,6 @@ public void Condition(Func<TSource, bool> condition)
return this;
}
- private static bool PassesDepthCheck(ResolutionContext context, int maxDepth)
- {
- if (context.InstanceCache.ContainsKey(context))
- {
- // return true if we already mapped this value and it's in the cache
- return true;
- }
-
- ResolutionContext contextCopy = context;
-
- int currentDepth = 1;
-
- // walk parents to determine current depth
- while (contextCopy.Parent != null)
- {
- if (contextCopy.SourceType == context.TypeMap.SourceType &&
- contextCopy.DestinationType == context.TypeMap.DestinationType)
- {
- // same source and destination types appear higher up in the hierarchy
- currentDepth++;
- }
- contextCopy = contextCopy.Parent;
- }
- return currentDepth <= maxDepth;
- }
public void Condition(Func<ResolutionContext, bool> condition)
{
View
444 src/AutoMapper/QueryableExtensions.cs
@@ -1,217 +1,237 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using AutoMapper.Impl;
-using AutoMapper.Internal;
-
-namespace AutoMapper.QueryableExtensions
-{
- public static class Extensions
- {
- private static readonly IDictionaryFactory DictionaryFactory = PlatformAdapter.Resolve<IDictionaryFactory>();
-
- private static readonly Internal.IDictionary<TypePair, LambdaExpression> _expressionCache
- = DictionaryFactory.CreateDictionary<TypePair, LambdaExpression>();
-
- /// <summary>
- /// Create an expression tree representing a mapping from the <typeparamref name="TSource"/> type to <typeparamref name="TDestination"/> type
- /// Includes flattening and expressions inside MapFrom member configuration
- /// </summary>
- /// <typeparam name="TSource">Source Type</typeparam>
- /// <typeparam name="TDestination">Destination Type</typeparam>
- /// <param name="mappingEngine">Mapping engine instance</param>
- /// <returns>Expression tree mapping source to destination type</returns>
- public static Expression<Func<TSource, TDestination>> CreateMapExpression<TSource, TDestination>(this IMappingEngine mappingEngine)
- {
- return (Expression<Func<TSource, TDestination>>)
- _expressionCache.GetOrAdd(new TypePair(typeof(TSource), typeof(TDestination)), tp => CreateMapExpression(mappingEngine, tp));
- }
-
-
- /// <summary>
- /// Extention method to project from a queryable using the static <see cref="Mapper.Engine"/> property
- /// Due to generic parameter inference, you need to call Project().To to execute the map
- /// </summary>
- /// <remarks>Projections are only calculated once and cached</remarks>
- /// <typeparam name="TSource">Source type</typeparam>
- /// <param name="source">Queryable source</param>
- /// <returns>Expression to project into</returns>
- public static IProjectionExpression Project<TSource>(
- this IQueryable<TSource> source)
- {
- return source.Project(Mapper.Engine);
- }
-
- /// <summary>
- /// Extention method to project from a queryable using the provided mapping engine
- /// Due to generic parameter inference, you need to call Project().To to execute the map
- /// </summary>
- /// <remarks>Projections are only calculated once and cached</remarks>
- /// <typeparam name="TSource">Source type</typeparam>
- /// <param name="source">Queryable source</param>
- /// <param name="mappingEngine">Mapping engine instance</param>
- /// <returns>Expression to project into</returns>
- public static IProjectionExpression Project<TSource>(
- this IQueryable<TSource> source, IMappingEngine mappingEngine)
- {
- return new ProjectionExpression<TSource>(source, mappingEngine);
- }
-
- private static LambdaExpression CreateMapExpression(IMappingEngine mappingEngine, TypePair typePair)
- {
- // this is the input parameter of this expression with name <variableName>
- ParameterExpression instanceParameter = Expression.Parameter(typePair.SourceType, "dto");
-
- var total = CreateMapExpression(mappingEngine, typePair, instanceParameter);
-
- return Expression.Lambda(total, instanceParameter);
- }
-
- private static Expression CreateMapExpression(IMappingEngine mappingEngine, TypePair typePair, Expression instanceParameter)
- {
- var typeMap = mappingEngine.ConfigurationProvider.FindTypeMapFor(typePair.SourceType, typePair.DestinationType);
-
- if (typeMap == null)
- {
- const string MessageFormat = "Missing map from {0} to {1}. Create using Mapper.CreateMap<{0}, {1}>.";
-
- var message = string.Format(MessageFormat, typePair.SourceType.Name, typePair.DestinationType.Name);
-
- throw new InvalidOperationException(message);
- }
-
- var bindings = CreateMemberBindings(mappingEngine, typePair.SourceType, typeMap, instanceParameter);
-
- Expression total = Expression.MemberInit(
- Expression.New(typePair.DestinationType),
- bindings.ToArray()
- );
-
- return total;
- }
-
- private static List<MemberBinding> CreateMemberBindings(IMappingEngine mappingEngine, Type typeIn, TypeMap typeMap,
- Expression instanceParameter)
- {
- var bindings = new List<MemberBinding>();
- foreach (var propertyMap in typeMap.GetPropertyMaps().Where(pm => pm.CanResolveValue()))
- {
- var result = propertyMap.ResolveExpression(typeIn, instanceParameter);
-
- var destinationMember = propertyMap.DestinationProperty.MemberInfo;
-
- MemberAssignment bindExpression;
-
- if (propertyMap.DestinationPropertyType.IsAssignableFrom(result.Type))
- {
- bindExpression = Expression.Bind(destinationMember, result.ResolutionExpression);
- }
- else if (propertyMap.DestinationPropertyType.GetInterfaces().Any(t => t.Name == "IEnumerable") &&
- propertyMap.DestinationPropertyType != typeof(string))
- {
- Type destinationListType = GetDestinationListTypeFor(propertyMap);
- Type sourceListType = null;
- // is list
-
+namespace AutoMapper.QueryableExtensions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using Impl;
+ using Internal;
+
+ public static class Extensions
+ {
+ private static readonly IDictionaryFactory DictionaryFactory = PlatformAdapter.Resolve<IDictionaryFactory>();
+
+ private static readonly Internal.IDictionary<TypePair, LambdaExpression> _expressionCache
+ = DictionaryFactory.CreateDictionary<TypePair, LambdaExpression>();
+
+ /// <summary>
+ /// Create an expression tree representing a mapping from the <typeparamref name="TSource"/> type to <typeparamref name="TDestination"/> type
+ /// Includes flattening and expressions inside MapFrom member configuration
+ /// </summary>
+ /// <typeparam name="TSource">Source Type</typeparam>
+ /// <typeparam name="TDestination">Destination Type</typeparam>
+ /// <param name="mappingEngine">Mapping engine instance</param>
+ /// <returns>Expression tree mapping source to destination type</returns>
+ public static Expression<Func<TSource, TDestination>> CreateMapExpression<TSource, TDestination>(
+ this IMappingEngine mappingEngine)
+ {
+ return (Expression<Func<TSource, TDestination>>)
+ _expressionCache.GetOrAdd(new TypePair(typeof (TSource), typeof (TDestination)),
+ tp => CreateMapExpression(mappingEngine, tp, DictionaryFactory.CreateDictionary<TypePair, int>()));
+ }
+
+
+ /// <summary>
+ /// Extention method to project from a queryable using the static <see cref="Mapper.Engine"/> property
+ /// Due to generic parameter inference, you need to call Project().To to execute the map
+ /// </summary>
+ /// <remarks>Projections are only calculated once and cached</remarks>
+ /// <typeparam name="TSource">Source type</typeparam>
+ /// <param name="source">Queryable source</param>
+ /// <returns>Expression to project into</returns>
+ public static IProjectionExpression Project<TSource>(
+ this IQueryable<TSource> source)
+ {
+ return source.Project(Mapper.Engine);
+ }
+
+ /// <summary>
+ /// Extention method to project from a queryable using the provided mapping engine
+ /// Due to generic parameter inference, you need to call Project().To to execute the map
+ /// </summary>
+ /// <remarks>Projections are only calculated once and cached</remarks>
+ /// <typeparam name="TSource">Source type</typeparam>
+ /// <param name="source">Queryable source</param>
+ /// <param name="mappingEngine">Mapping engine instance</param>
+ /// <returns>Expression to project into</returns>
+ public static IProjectionExpression Project<TSource>(
+ this IQueryable<TSource> source, IMappingEngine mappingEngine)
+ {
+ return new ProjectionExpression<TSource>(source, mappingEngine);
+ }
+
+ private static LambdaExpression CreateMapExpression(IMappingEngine mappingEngine, TypePair typePair,
+ Internal.IDictionary<TypePair, int> typePairCount)
+ {
+ // this is the input parameter of this expression with name <variableName>
+ ParameterExpression instanceParameter = Expression.Parameter(typePair.SourceType, "dto");
+
+ var total = CreateMapExpression(mappingEngine, typePair, instanceParameter, typePairCount);
+
+ return Expression.Lambda(total, instanceParameter);
+ }
+
+ private static Expression CreateMapExpression(IMappingEngine mappingEngine, TypePair typePair, Expression instanceParameter, Internal.IDictionary<TypePair, int> typePairCount)
+ {
+ var typeMap = mappingEngine.ConfigurationProvider.FindTypeMapFor(typePair.SourceType,
+ typePair.DestinationType);
+
+ if (typeMap == null)
+ {
+ const string MessageFormat = "Missing map from {0} to {1}. Create using Mapper.CreateMap<{0}, {1}>.";
+
+ var message = string.Format(MessageFormat, typePair.SourceType.Name, typePair.DestinationType.Name);
+
+ throw new InvalidOperationException(message);
+ }
+
+ var bindings = CreateMemberBindings(mappingEngine, typePair, typeMap, instanceParameter, typePairCount);
+
+ Expression total = Expression.MemberInit(
+ Expression.New(typePair.DestinationType),
+ bindings.ToArray()
+ );
+
+ return total;
+ }
+
+ private static List<MemberBinding> CreateMemberBindings(IMappingEngine mappingEngine, TypePair typePair,
+ TypeMap typeMap,
+ Expression instanceParameter, Internal.IDictionary<TypePair, int> typePairCount)
+ {
+ var bindings = new List<MemberBinding>();
+
+ var visitCount = typePairCount.AddOrUpdate(typePair, 0, (tp, i) => i + 1);
+
+ if (visitCount >= typeMap.MaxDepth)
+ return bindings;
+
+ foreach (var propertyMap in typeMap.GetPropertyMaps().Where(pm => pm.CanResolveValue()))
+ {
+ var result = propertyMap.ResolveExpression(typePair.SourceType, instanceParameter);
+
+ var destinationMember = propertyMap.DestinationProperty.MemberInfo;
+
+ MemberAssignment bindExpression;
+
+ if (propertyMap.DestinationPropertyType.IsAssignableFrom(result.Type))
+ {
+ bindExpression = Expression.Bind(destinationMember, result.ResolutionExpression);
+ }
+ else if (propertyMap.DestinationPropertyType.GetInterfaces().Any(t => t.Name == "IEnumerable") &&
+ propertyMap.DestinationPropertyType != typeof (string))
+ {
+ Type destinationListType = GetDestinationListTypeFor(propertyMap);
+ Type sourceListType = null;
+ // is list
+
sourceListType = result.Type.GetGenericArguments().First();
- var listTypePair = new TypePair(sourceListType, destinationListType);
-
- //var newVariableName = "t" + (i++);
- var transformedExpression = CreateMapExpression(mappingEngine, listTypePair);
-
- MethodCallExpression selectExpression = Expression.Call(
- typeof(Enumerable),
- "Select",
- new[] { sourceListType, destinationListType },
- result.ResolutionExpression,
- transformedExpression);
-
- if (typeof(IList<>).MakeGenericType(destinationListType).IsAssignableFrom(propertyMap.DestinationPropertyType))
- {
- var toListCallExpression = GetToListCallExpression(propertyMap, destinationListType, selectExpression);
- bindExpression = Expression.Bind(destinationMember, toListCallExpression);
+ var listTypePair = new TypePair(sourceListType, destinationListType);
+
+ //var newVariableName = "t" + (i++);
+ var transformedExpression = CreateMapExpression(mappingEngine, listTypePair, typePairCount);
+
+ MethodCallExpression selectExpression = Expression.Call(
+ typeof (Enumerable),
+ "Select",
+ new[] {sourceListType, destinationListType},
+ result.ResolutionExpression,
+ transformedExpression);
+
+ if (
+ typeof (IList<>).MakeGenericType(destinationListType)
+ .IsAssignableFrom(propertyMap.DestinationPropertyType))
+ {
+ var toListCallExpression = GetToListCallExpression(propertyMap, destinationListType,
+ selectExpression);
+ bindExpression = Expression.Bind(destinationMember, toListCallExpression);
}
- else if (typeof(ICollection<>).MakeGenericType(destinationListType).IsAssignableFrom(propertyMap.DestinationPropertyType))
+ else if (
+ typeof (ICollection<>).MakeGenericType(destinationListType)
+ .IsAssignableFrom(propertyMap.DestinationPropertyType))
{
- var toListCallExpression = GetToListCallExpression(propertyMap, destinationListType, selectExpression);
+ var toListCallExpression = GetToListCallExpression(propertyMap, destinationListType,
+ selectExpression);
bindExpression = Expression.Bind(destinationMember, toListCallExpression);
}
- else
- {
- // destination type implements ienumerable, but is not an ilist. allow deferred enumeration
- bindExpression = Expression.Bind(destinationMember, selectExpression);
- }
- }
- else if (result.Type != propertyMap.DestinationPropertyType &&
- // avoid nullable etc.
- propertyMap.DestinationPropertyType.BaseType != typeof(ValueType) &&
- propertyMap.DestinationPropertyType.BaseType != typeof(Enum))
- {
- var transformedExpression = CreateMapExpression(mappingEngine, new TypePair(result.Type, propertyMap.DestinationPropertyType),
- result.ResolutionExpression);
-
- bindExpression = Expression.Bind(destinationMember, transformedExpression);
- }
- else
- {
- throw new AutoMapperMappingException("Unable to create a map expression from " + result.Type + " to " + propertyMap.DestinationPropertyType);
- }
-
- bindings.Add(bindExpression);
- }
- return bindings;
- }
-
- private static Type GetDestinationListTypeFor(PropertyMap propertyMap)
- {
- Type destinationListType;
- if (propertyMap.DestinationPropertyType.IsArray)
- destinationListType = propertyMap.DestinationPropertyType.GetElementType();
- else
- destinationListType = propertyMap.DestinationPropertyType.GetGenericArguments().First();
- return destinationListType;
- }
-
- private static MethodCallExpression GetToListCallExpression(PropertyMap propertyMap, Type destinationListType, MethodCallExpression selectExpression)
- {
- return Expression.Call(
- typeof(Enumerable),
- propertyMap.DestinationPropertyType.IsArray ? "ToArray" : "ToList",
- new Type[] { destinationListType },
- selectExpression);
- }
- }
-
- /// <summary>
- /// Continuation to execute projection
- /// </summary>
- public interface IProjectionExpression
- {
- /// <summary>
- /// Projects the source type to the destination type given the mapping configuration
- /// </summary>
- /// <typeparam name="TResult">Destination type to map to</typeparam>
- /// <returns>Queryable result, use queryable extension methods to project and execute result</returns>
- IQueryable<TResult> To<TResult>();
- }
-
- public class ProjectionExpression<TSource> : IProjectionExpression
- {
- private readonly IQueryable<TSource> _source;
- private readonly IMappingEngine _mappingEngine;
-
- public ProjectionExpression(IQueryable<TSource> source, IMappingEngine mappingEngine)
- {
- _source = source;
- _mappingEngine = mappingEngine;
- }
-
- public IQueryable<TResult> To<TResult>()
- {
- Expression<Func<TSource, TResult>> expr = _mappingEngine.CreateMapExpression<TSource, TResult>();
-
- return _source.Select(expr);
- }
- }
-}
+ else
+ {
+ // destination type implements ienumerable, but is not an ilist. allow deferred enumeration
+ bindExpression = Expression.Bind(destinationMember, selectExpression);
+ }
+ }
+ else if (result.Type != propertyMap.DestinationPropertyType &&
+ // avoid nullable etc.
+ propertyMap.DestinationPropertyType.BaseType != typeof (ValueType) &&
+ propertyMap.DestinationPropertyType.BaseType != typeof (Enum))
+ {
+ var transformedExpression = CreateMapExpression(mappingEngine,
+ new TypePair(result.Type, propertyMap.DestinationPropertyType),
+ result.ResolutionExpression, typePairCount);
+
+ bindExpression = Expression.Bind(destinationMember, transformedExpression);
+ }
+ else
+ {
+ throw new AutoMapperMappingException("Unable to create a map expression from " + result.Type +
+ " to " + propertyMap.DestinationPropertyType);
+ }
+
+ bindings.Add(bindExpression);
+ }
+ return bindings;
+ }
+
+ private static Type GetDestinationListTypeFor(PropertyMap propertyMap)
+ {
+ Type destinationListType;
+ if (propertyMap.DestinationPropertyType.IsArray)
+ destinationListType = propertyMap.DestinationPropertyType.GetElementType();
+ else
+ destinationListType = propertyMap.DestinationPropertyType.GetGenericArguments().First();
+ return destinationListType;
+ }
+
+ private static MethodCallExpression GetToListCallExpression(PropertyMap propertyMap, Type destinationListType,
+ MethodCallExpression selectExpression)
+ {
+ return Expression.Call(
+ typeof (Enumerable),
+ propertyMap.DestinationPropertyType.IsArray ? "ToArray" : "ToList",
+ new[] {destinationListType},
+ selectExpression);
+ }
+ }
+
+ /// <summary>
+ /// Continuation to execute projection
+ /// </summary>
+ public interface IProjectionExpression
+ {
+ /// <summary>
+ /// Projects the source type to the destination type given the mapping configuration
+ /// </summary>
+ /// <typeparam name="TResult">Destination type to map to</typeparam>
+ /// <returns>Queryable result, use queryable extension methods to project and execute result</returns>
+ IQueryable<TResult> To<TResult>();
+ }
+
+ public class ProjectionExpression<TSource> : IProjectionExpression
+ {
+ private readonly IQueryable<TSource> _source;
+ private readonly IMappingEngine _mappingEngine;
+
+ public ProjectionExpression(IQueryable<TSource> source, IMappingEngine mappingEngine)
+ {
+ _source = source;
+ _mappingEngine = mappingEngine;
+ }
+
+ public IQueryable<TResult> To<TResult>()
+ {
+ Expression<Func<TSource, TResult>> expr = _mappingEngine.CreateMapExpression<TSource, TResult>();
+
+ return _source.Select(expr);
+ }
+ }
+}
View
37 src/AutoMapper/TypeMap.cs
@@ -27,6 +27,7 @@ public class TypeMap
private bool _sealed;
private Func<ResolutionContext, bool> _condition;
private ConstructorMap _constructorMap;
+ private int _maxDepth = Int32.MaxValue;
public TypeMap(TypeInfo sourceType, TypeInfo destinationType, MemberList memberList)
{
@@ -88,6 +89,16 @@ public Type DestinationType
public MemberList ConfiguredMemberList { get; private set; }
+ public int MaxDepth
+ {
+ get { return _maxDepth; }
+ set
+ {
+ _maxDepth = value;
+ SetCondition(o => PassesDepthCheck(o, value));
+ }
+ }
+
public IEnumerable<PropertyMap> GetPropertyMaps()
{
if (_sealed)
@@ -296,5 +307,31 @@ public SourceMemberConfig FindOrCreateSourceMemberConfigFor(MemberInfo sourceMem
return config;
}
+
+ private static bool PassesDepthCheck(ResolutionContext context, int maxDepth)
+ {
+ if (context.InstanceCache.ContainsKey(context))
+ {
+ // return true if we already mapped this value and it's in the cache
+ return true;
+ }
+
+ ResolutionContext contextCopy = context;
+
+ int currentDepth = 1;
+
+ // walk parents to determine current depth
+ while (contextCopy.Parent != null)
+ {
+ if (contextCopy.SourceType == context.TypeMap.SourceType &&
+ contextCopy.DestinationType == context.TypeMap.DestinationType)
+ {
+ // same source and destination types appear higher up in the hierarchy
+ currentDepth++;
+ }
+ contextCopy = contextCopy.Parent;
+ }
+ return currentDepth <= maxDepth;
+ }
}
}
View
5 src/UnitTests/ExpressionBridge.cs
@@ -270,6 +270,11 @@ public BEntity()
public virtual ICollection<AEntity> BP2 { get; set; }
}
+ public class C
+ {
+ public C Value { get; set; }
+ }
+
public class When_mapping_circular_references : AutoMapperSpecBase
{
private IQueryable<BEntity> _bei;

0 comments on commit 1c3db4e

Please sign in to comment.
Something went wrong with that request. Please try again.