diff --git a/src/NPoco/ColumnInfo.cs b/src/NPoco/ColumnInfo.cs
index 6432d270..ef8572cb 100644
--- a/src/NPoco/ColumnInfo.cs
+++ b/src/NPoco/ColumnInfo.cs
@@ -26,6 +26,10 @@ public class ColumnInfo
public string ReferenceMemberName { get; set; }
public MemberInfo MemberInfo { get; internal set; }
public bool ExactColumnNameMatch { get; set; }
+ ///
+ /// If this is set, this column's members will not be mapped beyond [HardDepthLimit] iterations (useful for complex, long-looping or aggregate data structures)
+ ///
+ public int? HardDepthLimit {get;set;}
public static ColumnInfo FromMemberInfo(MemberInfo mi)
{
@@ -38,6 +42,7 @@ public static ColumnInfo FromMemberInfo(MemberInfo mi)
var serializedColumnAttributes = attrs.OfType().ToArray();
var reference = attrs.OfType().ToArray();
var aliasColumn = attrs.OfType().FirstOrDefault();
+ var depthLimit = attrs.OfType().FirstOrDefault();
// Check if declaring poco has [ExplicitColumns] attribute
var explicitColumns = mi.DeclaringType.GetTypeInfo().GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Any();
@@ -50,6 +55,9 @@ public static ColumnInfo FromMemberInfo(MemberInfo mi)
ci.IgnoreColumn = true;
}
+ if (depthLimit != null )
+ ci.HardDepthLimit = depthLimit.DepthLimit;
+
if (complexMapping.Any())
{
ci.ComplexMapping = true;
diff --git a/src/NPoco/DepthLimitAttribute.cs b/src/NPoco/DepthLimitAttribute.cs
new file mode 100644
index 00000000..363c8492
--- /dev/null
+++ b/src/NPoco/DepthLimitAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace NPoco
+{
+ ///
+ /// Use to decorate columns, usually of lists, that have a chance of looping on themselves while being mapped
+ ///
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
+ public class DepthLimitAttribute : Attribute
+ {
+ ///
+ /// Default constructor sets the depth limit to 1, which is - do not map beyond one iteration.
+ ///
+ public DepthLimitAttribute()
+ {
+ DepthLimit = 1;
+ }
+ public DepthLimitAttribute(int maxDepth)
+ {
+ if (maxDepth < 1) throw new ArgumentOutOfRangeException("maxDepth", "Hard depth limit should be at least equal to 1");
+ DepthLimit = maxDepth;
+ }
+ public int DepthLimit { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs
index efc5a60e..d5282009 100644
--- a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs
+++ b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs
@@ -61,6 +61,7 @@ public interface IManyColumnBuilder
IManyColumnBuilder WithDbType(Type type);
IManyColumnBuilder WithDbType();
IManyColumnBuilder Reference(Expression> member);
+ IManyColumnBuilder LimitMappingDepth(int limit = 1);
}
public class ManyColumnBuilder : IManyColumnBuilder
@@ -96,6 +97,20 @@ public IManyColumnBuilder Reference(Expression> mem
_columnDefinition.ReferenceMember = MemberHelper.GetMembers(member).Last();
return this;
}
+ ///
+ /// Sets the hard mapping depth limit for this column's definition
+ ///
+ /// integer hard depth limit. Defaults to and can't be less than 1
+ /// The ManyColumnBuilder
+ public IManyColumnBuilder LimitMappingDepth(int limit = 1)
+ {
+ if ( limit < 1 )
+ throw new ArgumentOutOfRangeException("limit", "The mapping depth limit must always be at least equal to 1");
+
+ _columnDefinition.HardDepthLimit = limit;
+
+ return this;
+ }
}
public interface IColumnBuilder
@@ -118,6 +133,7 @@ public interface IColumnBuilder
IColumnBuilder ValueObject();
IColumnBuilder ValueObject(Expression> member);
IColumnBuilder ForceToUtc(bool enabled);
+ IColumnBuilder LimitMappingDepth(int limit = 1);
}
public class ColumnBuilder : IColumnBuilder
@@ -253,5 +269,19 @@ public IColumnBuilder ForceToUtc(bool enabled)
_columnDefinition.ForceUtc = enabled;
return this;
}
+ ///
+ /// Sets the hard mapping depth limit for this column's definition
+ ///
+ /// integer hard depth limit. Defaults to and can't be less than 1
+ /// The ColumnBuilder
+ public IColumnBuilder LimitMappingDepth(int limit = 1)
+ {
+ if ( limit < 1 )
+ throw new ArgumentOutOfRangeException("limit", "The mapping depth limit must always be at least equal to 1");
+
+ _columnDefinition.HardDepthLimit = limit;
+
+ return this;
+ }
}
}
\ No newline at end of file
diff --git a/src/NPoco/FluentMappings/ColumnDefinition.cs b/src/NPoco/FluentMappings/ColumnDefinition.cs
index 3a9b064e..0fdddd6c 100644
--- a/src/NPoco/FluentMappings/ColumnDefinition.cs
+++ b/src/NPoco/FluentMappings/ColumnDefinition.cs
@@ -26,5 +26,9 @@ public class ColumnDefinition
public bool? ValueObjectColumn { get; set; }
public string ValueObjectColumnName { get; set; }
public bool? ExactColumnNameMatch { get; set; }
+ ///
+ /// If this is set, this column's members will not be mapped beyond [HardDepthLimit] iterations (useful for complex, long-looping or aggregate data structures)
+ ///
+ public int? HardDepthLimit {get;set;}
}
}
\ No newline at end of file
diff --git a/src/NPoco/FluentMappings/ConventionScannerSettings.cs b/src/NPoco/FluentMappings/ConventionScannerSettings.cs
index f87de9c7..15a94107 100644
--- a/src/NPoco/FluentMappings/ConventionScannerSettings.cs
+++ b/src/NPoco/FluentMappings/ConventionScannerSettings.cs
@@ -31,6 +31,10 @@ public ConventionScannerSettings()
public Func DbColumnsNamed { get; set; }
public Func AliasNamed { get; set; }
public Func DbColumnTypesAs { get; set; }
+ ///
+ /// Set the hard depth limit for the columns. Null leaves the limit unset.
+ ///
+ public Func HardDepthLimitAs {get;set;}
public List> IgnorePropertiesWhere { get; set; }
public Func VersionPropertiesWhere { get; set; }
public Func VersionPropertyTypeAs { get; set; }
diff --git a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs
index 21894af9..827057aa 100644
--- a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs
+++ b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs
@@ -113,10 +113,14 @@ private static IEnumerable GetColumnDefinitions(ConventionScan
foreach (var columnDefinition in columnDefinitions)
{
+
+ if ( columnDefinition.HardDepthLimit >= members.Count)
+ continue;
+
yield return columnDefinition;
}
- var referenceDbColumnsNamed = scannerSettings.ReferenceDbColumnsNamed(member);
+ var referenceDbColumnsNamed = scannerSettings.ReferenceDbColumnsNamed(member);
yield return new ColumnDefinition()
{
@@ -128,6 +132,7 @@ private static IEnumerable GetColumnDefinitions(ConventionScan
ReferenceMember = null,
ResultColumn = scannerSettings.ResultPropertiesWhere(member),
DbColumnName = referenceProperty ? referenceDbColumnsNamed : null,
+ HardDepthLimit = scannerSettings.HardDepthLimitAs(member)
};
}
else
@@ -151,6 +156,7 @@ private static IEnumerable GetColumnDefinitions(ConventionScan
columnDefinition.Serialized = scannerSettings.SerializedWhere(member);
columnDefinition.IsComplexMapping = scannerSettings.ComplexPropertiesWhere(member);
columnDefinition.ValueObjectColumn = scannerSettings.ValueObjectColumnWhere(member);
+ columnDefinition.HardDepthLimit = scannerSettings.HardDepthLimitAs(member);
yield return columnDefinition;
}
}
@@ -182,7 +188,8 @@ private static ConventionScannerSettings ProcessSettings(Action ReflectionUtils.GetCustomAttributes(x, typeof(ColumnAttribute)).Any(),
ValueObjectColumnWhere = x => x.GetMemberInfoType().GetInterfaces().Any(y => y == typeof(IValueObject)),
Lazy = false,
- MapNestedTypesWhen = x => false
+ MapNestedTypesWhen = x => false,
+ HardDepthLimitAs = x => null //no depth limit by default
};
scanner.Invoke(new ConventionScanner(defaultScannerSettings));
return defaultScannerSettings;
@@ -227,6 +234,7 @@ private static void MergeAttributeOverrides(Dictionary con
columnDefinition.Value.ForceUtc = columnInfo.ForceToUtc;
columnDefinition.Value.Serialized = columnInfo.SerializedColumn;
columnDefinition.Value.ValueObjectColumn = columnInfo.ValueObjectColumn;
+ columnDefinition.Value.HardDepthLimit = columnInfo.HardDepthLimit;
}
}
}
@@ -278,6 +286,7 @@ private static void MergeOverrides(Dictionary config, Mapp
convColDefinition.ValueObjectColumn = overrideColumnDefinition.Value.ValueObjectColumn ?? convColDefinition.ValueObjectColumn;
convColDefinition.ValueObjectColumnName = overrideColumnDefinition.Value.ValueObjectColumnName ?? convColDefinition.ValueObjectColumnName;
convColDefinition.ExactColumnNameMatch = overrideColumnDefinition.Value.ExactColumnNameMatch ?? convColDefinition.ExactColumnNameMatch;
+ convColDefinition.HardDepthLimit = overrideColumnDefinition.Value.HardDepthLimit ?? convColDefinition.HardDepthLimit;
}
}
}
diff --git a/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs b/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs
index cf4b7bc4..2d9043c5 100644
--- a/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs
+++ b/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs
@@ -116,6 +116,8 @@ protected override ColumnInfo GetColumnInfo(MemberInfo mi, Type type)
if (isColumnDefined && (typeConfig.ColumnConfiguration[key].IgnoreColumn.HasValue && typeConfig.ColumnConfiguration[key].IgnoreColumn.Value))
columnInfo.IgnoreColumn = true;
+ if (isColumnDefined) columnInfo.HardDepthLimit = typeConfig.ColumnConfiguration[key].HardDepthLimit;
+
// Work out the DB column name
if (isColumnDefined)
{
diff --git a/src/NPoco/PocoDataBuilder.cs b/src/NPoco/PocoDataBuilder.cs
index f59e8f7e..441f761b 100644
--- a/src/NPoco/PocoDataBuilder.cs
+++ b/src/NPoco/PocoDataBuilder.cs
@@ -123,10 +123,34 @@ private static IEnumerable GetPocoColumns(IEnumerable me
}
}
- public IEnumerable GetPocoMembers(ColumnInfo[] columnInfos, List memberInfos, string prefix = null)
+ private int _LongestUnbrokenTypeSequence(IEnumerable input)
{
+ var iterator = input.GetEnumerator();
+ Dictionary lastPositions = new Dictionary();
+ int maxLen = 0;
+ int windowStart = 0;
+ int windowEnd = 0;
+
+ while (iterator.MoveNext())
+ {
+ if (lastPositions.ContainsKey(iterator.Current))
+ {
+ windowStart = Math.Max(windowStart, lastPositions[iterator.Current] + 1);
+ }
+
+ lastPositions[iterator.Current] = windowEnd;
+ maxLen = Math.Max(maxLen, windowEnd - windowStart + 1);
+ windowEnd++;
+ }
+
+ return maxLen;
+ }
+
+ public IEnumerable GetPocoMembers(ColumnInfo[] columnInfos, List memberInfos, string prefix = null)
+ {
var capturedMembers = memberInfos.ToArray();
var capturedPrefix = prefix;
+
foreach (var columnInfo in columnInfos)
{
if (columnInfo.IgnoreColumn)
@@ -145,6 +169,14 @@ public IEnumerable GetPocoMembers(ColumnInfo[] columnInfos, List
TableInfoPlan childTableInfoPlan = null;
var members = new List(capturedMembers) { columnInfo.MemberInfo };
+ // Never map anything beyond the hard set limit, if one has been set
+ if ( columnInfo.HardDepthLimit.HasValue)
+ {
+ if (_LongestUnbrokenTypeSequence(members.Select(m => m.GetMemberInfoType())) > columnInfo.HardDepthLimit.Value)
+ continue;
+ }
+
+
if (columnInfo.ComplexMapping || columnInfo.ReferenceType != ReferenceType.None)
{
if (capturedMembers.GroupBy(x => x.GetMemberInfoType()).Any(x => x.Count() >= 2))