Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of a Max Depth for member scanning #638

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/NPoco/ColumnInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public class ColumnInfo
public string ReferenceMemberName { get; set; }
public MemberInfo MemberInfo { get; internal set; }
public bool ExactColumnNameMatch { get; set; }
/// <summary>
/// If this is set, this column's members will not be mapped beyond [HardDepthLimit] iterations (useful for complex, long-looping or aggregate data structures)
/// </summary>
public int? HardDepthLimit {get;set;}

public static ColumnInfo FromMemberInfo(MemberInfo mi)
{
Expand All @@ -38,6 +42,7 @@ public static ColumnInfo FromMemberInfo(MemberInfo mi)
var serializedColumnAttributes = attrs.OfType<SerializedColumnAttribute>().ToArray();
var reference = attrs.OfType<ReferenceAttribute>().ToArray();
var aliasColumn = attrs.OfType<AliasAttribute>().FirstOrDefault();
var depthLimit = attrs.OfType<DepthLimitAttribute>().FirstOrDefault();

// Check if declaring poco has [ExplicitColumns] attribute
var explicitColumns = mi.DeclaringType.GetTypeInfo().GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Any();
Expand All @@ -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;
Expand Down
25 changes: 25 additions & 0 deletions src/NPoco/DepthLimitAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace NPoco
{
/// <summary>
/// Use to decorate columns, usually of lists, that have a chance of looping on themselves while being mapped
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DepthLimitAttribute : Attribute
{
/// <summary>
/// Default constructor sets the depth limit to 1, which is - do not map beyond one iteration.
/// </summary>
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; }
}
}
30 changes: 30 additions & 0 deletions src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public interface IManyColumnBuilder<TModel>
IManyColumnBuilder<TModel> WithDbType(Type type);
IManyColumnBuilder<TModel> WithDbType<T>();
IManyColumnBuilder<TModel> Reference(Expression<Func<TModel, object>> member);
IManyColumnBuilder<TModel> LimitMappingDepth(int limit = 1);
}

public class ManyColumnBuilder<TModel> : IManyColumnBuilder<TModel>
Expand Down Expand Up @@ -96,6 +97,20 @@ public IManyColumnBuilder<TModel> Reference(Expression<Func<TModel, object>> mem
_columnDefinition.ReferenceMember = MemberHelper<TModel>.GetMembers(member).Last();
return this;
}
/// <summary>
/// Sets the hard mapping depth limit for this column's definition
/// </summary>
/// <param name="limit">integer hard depth limit. Defaults to and can't be less than 1</param>
/// <returns>The ManyColumnBuilder</returns>
public IManyColumnBuilder<TModel> 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<TModel>
Expand All @@ -118,6 +133,7 @@ public interface IColumnBuilder<TModel>
IColumnBuilder<TModel> ValueObject();
IColumnBuilder<TModel> ValueObject(Expression<Func<TModel, object>> member);
IColumnBuilder<TModel> ForceToUtc(bool enabled);
IColumnBuilder<TModel> LimitMappingDepth(int limit = 1);
}

public class ColumnBuilder<TModel> : IColumnBuilder<TModel>
Expand Down Expand Up @@ -253,5 +269,19 @@ public IColumnBuilder<TModel> ForceToUtc(bool enabled)
_columnDefinition.ForceUtc = enabled;
return this;
}
/// <summary>
/// Sets the hard mapping depth limit for this column's definition
/// </summary>
/// <param name="limit">integer hard depth limit. Defaults to and can't be less than 1</param>
/// <returns>The ColumnBuilder</returns>
public IColumnBuilder<TModel> 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;
}
}
}
4 changes: 4 additions & 0 deletions src/NPoco/FluentMappings/ColumnDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@ public class ColumnDefinition
public bool? ValueObjectColumn { get; set; }
public string ValueObjectColumnName { get; set; }
public bool? ExactColumnNameMatch { get; set; }
/// <summary>
/// If this is set, this column's members will not be mapped beyond [HardDepthLimit] iterations (useful for complex, long-looping or aggregate data structures)
/// </summary>
public int? HardDepthLimit {get;set;}
}
}
4 changes: 4 additions & 0 deletions src/NPoco/FluentMappings/ConventionScannerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public ConventionScannerSettings()
public Func<MemberInfo, string> DbColumnsNamed { get; set; }
public Func<MemberInfo, string> AliasNamed { get; set; }
public Func<MemberInfo, Type> DbColumnTypesAs { get; set; }
/// <summary>
/// Set the hard depth limit for the columns. Null leaves the limit unset.
/// </summary>
public Func<MemberInfo, int?> HardDepthLimitAs {get;set;}
public List<Func<MemberInfo, bool>> IgnorePropertiesWhere { get; set; }
public Func<MemberInfo, bool> VersionPropertiesWhere { get; set; }
public Func<MemberInfo, VersionColumnType> VersionPropertyTypeAs { get; set; }
Expand Down
13 changes: 11 additions & 2 deletions src/NPoco/FluentMappings/FluentMappingConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,14 @@ private static IEnumerable<ColumnDefinition> 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()
{
Expand All @@ -128,6 +132,7 @@ private static IEnumerable<ColumnDefinition> GetColumnDefinitions(ConventionScan
ReferenceMember = null,
ResultColumn = scannerSettings.ResultPropertiesWhere(member),
DbColumnName = referenceProperty ? referenceDbColumnsNamed : null,
HardDepthLimit = scannerSettings.HardDepthLimitAs(member)
};
}
else
Expand All @@ -151,6 +156,7 @@ private static IEnumerable<ColumnDefinition> 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;
}
}
Expand Down Expand Up @@ -182,7 +188,8 @@ private static ConventionScannerSettings ProcessSettings(Action<IConventionScann
DbColumnWhere = x => 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;
Expand Down Expand Up @@ -227,6 +234,7 @@ private static void MergeAttributeOverrides(Dictionary<Type, TypeDefinition> con
columnDefinition.Value.ForceUtc = columnInfo.ForceToUtc;
columnDefinition.Value.Serialized = columnInfo.SerializedColumn;
columnDefinition.Value.ValueObjectColumn = columnInfo.ValueObjectColumn;
columnDefinition.Value.HardDepthLimit = columnInfo.HardDepthLimit;
}
}
}
Expand Down Expand Up @@ -278,6 +286,7 @@ private static void MergeOverrides(Dictionary<Type, TypeDefinition> 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;
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
34 changes: 33 additions & 1 deletion src/NPoco/PocoDataBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,34 @@ private static IEnumerable<PocoColumn> GetPocoColumns(IEnumerable<PocoMember> me
}
}

public IEnumerable<PocoMemberPlan> GetPocoMembers(ColumnInfo[] columnInfos, List<MemberInfo> memberInfos, string prefix = null)
private int _LongestUnbrokenTypeSequence(IEnumerable<Type> input)
{
var iterator = input.GetEnumerator();
Dictionary<Type, int> lastPositions = new Dictionary<Type, int>();
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<PocoMemberPlan> GetPocoMembers(ColumnInfo[] columnInfos, List<MemberInfo> memberInfos, string prefix = null)
{
var capturedMembers = memberInfos.ToArray();
var capturedPrefix = prefix;

foreach (var columnInfo in columnInfos)
{
if (columnInfo.IgnoreColumn)
Expand All @@ -145,6 +169,14 @@ public IEnumerable<PocoMemberPlan> GetPocoMembers(ColumnInfo[] columnInfos, List
TableInfoPlan childTableInfoPlan = null;
var members = new List<MemberInfo>(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))
Expand Down