From c3c888a6b49e8d83ae7f8d944f12fc6e3e3ad827 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri <7004080+alirezanet@users.noreply.github.com> Date: Fri, 17 Sep 2021 17:55:51 +0430 Subject: [PATCH] v2.0.0-beta.3 (#19) * add issue #17 test * Make isNestedCollection Readonly * Chain filtering on nested collections using parenthesis fixed #17 * update to v2.0.0-beta.3 --- .../Gridify.EntityFramework.csproj | 2 +- src/Gridify/GMap.cs | 2 +- src/Gridify/Gridify.csproj | 2 +- src/Gridify/GridifyExtensions.cs | 4 +- src/Gridify/IGMap.cs | 2 +- .../Syntax/SyntaxTreeToQueryConvertor.cs | 138 ++++++++++++----- .../GridifyNestedCollectionTests.cs | 146 +++++++++++++++--- 7 files changed, 229 insertions(+), 67 deletions(-) diff --git a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj index bc52cb68..382a7d8a 100644 --- a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj +++ b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj @@ -9,7 +9,7 @@ netstandard2.0 Gridify.EntityFramework - 2.0.0-beta.2 + 2.0.0-beta.3 Alireza Sabouri TuxTeam Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/GMap.cs b/src/Gridify/GMap.cs index 4db19818..126eefda 100644 --- a/src/Gridify/GMap.cs +++ b/src/Gridify/GMap.cs @@ -8,7 +8,7 @@ public class GMap : IGMap public string From { get; set; } public Expression> To { get; set; } public Func? Convertor { get; set; } - public bool IsNestedCollection { get; set; } + public bool IsNestedCollection { get; } public GMap(string from, Expression> to, Func? convertor = null, bool isNestedCollection = false) { diff --git a/src/Gridify/Gridify.csproj b/src/Gridify/Gridify.csproj index 399b68a0..3e5b4cda 100644 --- a/src/Gridify/Gridify.csproj +++ b/src/Gridify/Gridify.csproj @@ -3,7 +3,7 @@ netstandard2.0 Gridify - 2.0.0-beta.2 + 2.0.0-beta.3 Alireza Sabouri TuxTeam Gridify, Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/GridifyExtensions.cs b/src/Gridify/GridifyExtensions.cs index 69d65bf8..8479041f 100644 --- a/src/Gridify/GridifyExtensions.cs +++ b/src/Gridify/GridifyExtensions.cs @@ -49,7 +49,7 @@ public static Expression> GetFilteringExpression(this IGridifyF if (syntaxTree.Diagnostics.Any()) throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); - var queryExpression = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); + var (queryExpression,_) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); if (queryExpression == null) throw new GridifyQueryException("Can not create expression with current data"); return queryExpression; } @@ -229,7 +229,7 @@ public static IQueryable ApplyFiltering(this IQueryable query, string? if (syntaxTree.Diagnostics.Any()) throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); - var queryExpression = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); + var (queryExpression,_) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); query = query.Where(queryExpression); diff --git a/src/Gridify/IGMap.cs b/src/Gridify/IGMap.cs index 6f2b29f0..2c6e5b43 100644 --- a/src/Gridify/IGMap.cs +++ b/src/Gridify/IGMap.cs @@ -8,6 +8,6 @@ public interface IGMap string From { get; set; } Expression> To { get; set; } Func? Convertor { get; set; } - bool IsNestedCollection { get; set; } + bool IsNestedCollection { get; } } } \ No newline at end of file diff --git a/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs b/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs index e3738634..6550d65e 100644 --- a/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs +++ b/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.ComponentModel; +using System.Data; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; @@ -10,23 +11,31 @@ namespace Gridify.Syntax { public static class ExpressionToQueryConvertor { - private static Expression>? ConvertBinaryExpressionSyntaxToQuery(BinaryExpressionSyntax binarySyntax, IGridifyMapper mapper) + private static (Expression> Expression, bool IsNested)? ConvertBinaryExpressionSyntaxToQuery( + BinaryExpressionSyntax binarySyntax, IGridifyMapper mapper) { - var left = (binarySyntax.Left as FieldExpressionSyntax)?.FieldToken.Text.Trim(); - var right = (binarySyntax.Right as ValueExpressionSyntax)?.ValueToken.Text; - var op = binarySyntax.OperatorToken; + var left = (binarySyntax.Left as FieldExpressionSyntax)?.FieldToken.Text.Trim(); + var right = (binarySyntax.Right as ValueExpressionSyntax)?.ValueToken.Text; + var op = binarySyntax.OperatorToken; - if (left == null || right == null) return null; + if (left == null || right == null) return null; - var gMap = mapper.GetGMap(left); + var gMap = mapper.GetGMap(left); - if (gMap == null) return null; + if (gMap == null) return null; - if (gMap.IsNestedCollection) - return GenerateNestedExpression(mapper, gMap, right, op); - - return GenerateExpression(gMap.To.Body, gMap.To.Parameters[0], right, - op, mapper.Configuration.AllowNullSearch, gMap.Convertor) as Expression>; + if (gMap.IsNestedCollection) + { + var result = GenerateNestedExpression(mapper, gMap, right, op); + if (result == null) return null; + return (result, gMap.IsNestedCollection); + } + else + { + if (GenerateExpression(gMap.To.Body, gMap.To.Parameters[0], right, + op, mapper.Configuration.AllowNullSearch, gMap.Convertor) is not Expression> result) return null; + return (result, false); + } } private static Expression>? GenerateNestedExpression( @@ -58,15 +67,17 @@ private static LambdaExpression ParseMethodCallExpression(MethodCallExpression e { case MemberExpression member: return GetAnyExpression(member, predicate); - case MethodCallExpression subExp when subExp.Method.Name == "SelectMany" && subExp.Arguments.Last() is LambdaExpression { Body: MemberExpression lambdaMember }: + case MethodCallExpression subExp when subExp.Method.Name == "SelectMany" && + subExp.Arguments.Last() is LambdaExpression {Body: MemberExpression lambdaMember}: { var newPredicate = GetAnyExpression(lambdaMember, predicate); return ParseMethodCallExpression(subExp, newPredicate); } - case MethodCallExpression subExp when subExp.Method.Name == "Select" && subExp.Arguments.Last() is LambdaExpression { Body: MemberExpression lambdaMember } lambda: + case MethodCallExpression subExp when subExp.Method.Name == "Select" && subExp.Arguments.Last() is LambdaExpression + {Body: MemberExpression lambdaMember} lambda: { var newExp = new PredicateBuilder.ReplaceExpressionVisitor(predicate.Parameters[0], lambdaMember).Visit(predicate.Body); - var newPredicate = GetExpressionWithNullCheck(lambdaMember, lambda.Parameters[0], newExp!); + var newPredicate = GetExpressionWithNullCheck(lambdaMember, lambda.Parameters[0], newExp!); return ParseMethodCallExpression(subExp, newPredicate); } default: @@ -76,8 +87,9 @@ private static LambdaExpression ParseMethodCallExpression(MethodCallExpression e private static ParameterExpression GetParameterExpression(MemberExpression member) { - return Expression.Parameter(member.Expression.Type, member.Expression.ToString()); + return Expression.Parameter(member.Expression.Type, member.Expression.ToString()); } + private static LambdaExpression GetAnyExpression(MemberExpression member, Expression predicate) { var param = GetParameterExpression(member); @@ -88,15 +100,13 @@ private static LambdaExpression GetAnyExpression(MemberExpression member, Expres var anyExp = Expression.Call(anyMethod, prop, predicate); return GetExpressionWithNullCheck(prop, param, anyExp); - - // return Expression.Lambda(anyExp, param); } private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop, ParameterExpression param, Expression right) { var nullChecker = Expression.NotEqual(prop, Expression.Constant(null)); var exp = Expression.AndAlso(nullChecker, right); - return Expression.Lambda(exp, param); + return Expression.Lambda(exp, param); } private static LambdaExpression? GenerateExpression( @@ -108,7 +118,7 @@ private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop Func? convertor) { // Remove the boxing for value types - if (body.NodeType == ExpressionType.Convert) body = ((UnaryExpression)body).Operand; + if (body.NodeType == ExpressionType.Convert) body = ((UnaryExpression) body).Operand; object? value = stringValue; @@ -213,15 +223,17 @@ private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop private static MethodInfo GetAnyMethod(Type @type) => typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(@type); - private static MethodInfo GetEndsWithMethod() => typeof(string).GetMethod("EndsWith", new[] { typeof(string) })!; - private static MethodInfo GetStartWithMethod() => typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!; + private static MethodInfo GetEndsWithMethod() => typeof(string).GetMethod("EndsWith", new[] {typeof(string)})!; + + private static MethodInfo GetStartWithMethod() => typeof(string).GetMethod("StartsWith", new[] {typeof(string)})!; - private static MethodInfo GetContainsMethod() => typeof(string).GetMethod("Contains", new[] { typeof(string) })!; + private static MethodInfo GetContainsMethod() => typeof(string).GetMethod("Contains", new[] {typeof(string)})!; private static MethodInfo GetToStringMethod() => typeof(object).GetMethod("ToString")!; - internal static Expression> GenerateQuery(ExpressionSyntax expression, IGridifyMapper mapper) + internal static (Expression> Expression, bool IsNested) + GenerateQuery(ExpressionSyntax expression, IGridifyMapper mapper,bool isParenthesisOpen=false) { while (true) switch (expression.Kind) @@ -233,38 +245,92 @@ internal static Expression> GenerateQuery(ExpressionSyntax expr if (bExp!.Left is FieldExpressionSyntax && bExp.Right is ValueExpressionSyntax) return ConvertBinaryExpressionSyntaxToQuery(bExp, mapper) ?? throw new GridifyFilteringException("Invalid expression"); - Expression> leftQuery; - Expression> rightQuery; - - + (Expression> exp,bool isNested) leftQuery; + (Expression> exp,bool isNested) rightQuery; + if (bExp.Left is ParenthesizedExpressionSyntax lpExp) - leftQuery = GenerateQuery(lpExp.Expression, mapper); + { + leftQuery = GenerateQuery(lpExp.Expression, mapper, true); + } else leftQuery = GenerateQuery(bExp.Left, mapper); if (bExp.Right is ParenthesizedExpressionSyntax rpExp) - rightQuery = GenerateQuery(rpExp.Expression, mapper); + rightQuery = GenerateQuery(rpExp.Expression, mapper,true); else rightQuery = GenerateQuery(bExp.Right, mapper); + // check for nested collections + if (isParenthesisOpen && + CheckIfCanMerge(leftQuery, rightQuery,bExp.OperatorToken.Kind) is Expression> mergedResult) + return (mergedResult,true); - return bExp.OperatorToken.Kind switch + var result = bExp.OperatorToken.Kind switch { - SyntaxKind.And => leftQuery.And(rightQuery), - SyntaxKind.Or => leftQuery.Or(rightQuery), + SyntaxKind.And => leftQuery.exp.And(rightQuery.exp), + SyntaxKind.Or => leftQuery.exp.Or(rightQuery.exp), _ => throw new GridifyFilteringException($"Invalid expression Operator '{bExp.OperatorToken.Kind}'") }; + return (result, false); } - case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ParenthesizedExpression: // first entrypoint only { var pExp = expression as ParenthesizedExpressionSyntax; - expression = pExp!.Expression; - continue; + return GenerateQuery(pExp!.Expression, mapper, true); } default: throw new GridifyFilteringException($"Invalid expression format '{expression.Kind}'."); } } + + private static LambdaExpression? CheckIfCanMerge((Expression> exp, bool isNested) leftQuery, + (Expression> exp, bool isNested) rightQuery,SyntaxKind op) + { + if (leftQuery.isNested && rightQuery.isNested) + { + var leftExp = ParseNestedExpression(leftQuery.exp.Body); + var rightExp = ParseNestedExpression(rightQuery.exp.Body); + + if (leftExp.Arguments.First() is MemberExpression leftMember && + rightExp.Arguments.First() is MemberExpression rightMember && + leftMember.Type == rightMember.Type) + { + // we can merge + var leftLambda = leftExp.Arguments.Last() as LambdaExpression; + var rightLambda = rightExp.Arguments.Last() as LambdaExpression; + + if (leftLambda is null || rightLambda is null) + return null; + + var visitedRight= new PredicateBuilder.ReplaceExpressionVisitor(rightLambda.Parameters[0], leftLambda.Parameters[0]) + .Visit(rightLambda.Body); + + var mergedExpression = op switch + { + SyntaxKind.And => Expression.AndAlso(leftLambda.Body, visitedRight), + SyntaxKind.Or => Expression.OrElse(leftLambda.Body, visitedRight), + _ => throw new InvalidOperationException() + }; + + var mergedLambda = Expression.Lambda(mergedExpression, leftLambda.Parameters); + var newLambda = GetAnyExpression(leftMember, mergedLambda) as Expression>; + return newLambda; + } + } + return null; + } + + private static MethodCallExpression ParseNestedExpression(Expression exp) + { + return exp switch + { + BinaryExpression {Right: MethodCallExpression cExp} => cExp, + MethodCallExpression mcExp => mcExp, + _ => throw new InvalidExpressionException() + }; + } + + } } \ No newline at end of file diff --git a/test/Gridify.Tests/GridifyNestedCollectionTests.cs b/test/Gridify.Tests/GridifyNestedCollectionTests.cs index 6f6dfe59..ad56e7dd 100644 --- a/test/Gridify.Tests/GridifyNestedCollectionTests.cs +++ b/test/Gridify.Tests/GridifyNestedCollectionTests.cs @@ -34,6 +34,75 @@ public void Filtering_OnThirdLevelNestedProperty() Assert.Equal(expected, actual); Assert.True(actual.Any()); } + + [Fact] //https://github.com/alirezanet/Gridify/issues/17 #17 + public void Filtering_OnThirdLevelNestedPropertyWithMultipleChainedConditions() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) + .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); + + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("(Level2List_Id = 101, Level2List_Level3List_Property1 >= 3) , id < 10", gm) + .ToList(); + + + var expected = _fakeRepository3Nesting.AsQueryable().Where( + l1 => l1.Level2List != null && + l1.Level2List.Any(l2 => l2.Id == 101 && + l2.Level3List != null && + l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.False(actual.Any()); + } + + [Fact] //https://github.com/alirezanet/Gridify/issues/17 #17 + public void Filtering_OnThirdLevelNestedPropertyWithMultipleUnChainedConditions() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) + .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); + + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("Level2List_Id = 101, Level2List_Level3List_Property1 >= 3,id < 10", gm) + .ToList(); + + + var expected = _fakeRepository3Nesting.AsQueryable().Where( + l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.Id == 101) && + l1.Level2List.Any(l2 => l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void Filtering_OnThirdLevelNestedPropertyWithMultipleUnChainedConditionsWithNestedParenthesis() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) + .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); + + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("( (id < 10 ), Level2List_Id = 101, Level2List_Level3List_Property1 >= 3)", gm) + .ToList(); + + + var expected = _fakeRepository3Nesting.AsQueryable().Where( + l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.Id == 101) && + l1.Level2List.Any(l2 => l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + [Fact] public void Filtering_OnSecondLevelNestedProperty() { @@ -52,26 +121,27 @@ public void Filtering_OnSecondLevelNestedProperty() Assert.Equal(expected, actual); Assert.True(actual.Any()); } + [Fact] public void Filtering_OnThirdLevelNestedPropertyUsingSecondLevelProp() { var gm = new GridifyMapper() .GenerateMappings() - .AddMap("lvl", l1 => l1.Level2List.Select(l2 => l2.ChildProp).SelectMany(sl2 => sl2.Level3List).Select(l3=>l3.Level)); + .AddMap("lvl", l1 => l1.Level2List.Select(l2 => l2.ChildProp).SelectMany(sl2 => sl2.Level3List).Select(l3 => l3.Level)); var actual = _fakeRepository3Nesting.AsQueryable() .ApplyFiltering("lvl < 2", gm) .ToList(); - var expected = _fakeRepository3Nesting.Where(l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.ChildProp != null && l2.ChildProp.Level3List != null && - l2.ChildProp.Level3List.Any(l3 => l3.Level < 2))).ToList(); + var expected = _fakeRepository3Nesting.Where(l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.ChildProp != null && + l2.ChildProp.Level3List != null && + l2.ChildProp.Level3List.Any(l3 => l3.Level < 2))).ToList(); Assert.Equal(expected.Count, actual.Count); Assert.Equal(expected, actual); Assert.True(actual.Any()); - } - + } #region TestData @@ -80,13 +150,13 @@ private IEnumerable GetSampleDataWith3Nesting() { var subLvl2 = new ChildProp() { - Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 1 }} + Level3List = new List() {new Level3() {Property1 = 2.0, Property2 = 100.0, Level = 1}} }; var subLvl2_2 = new ChildProp() { - Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 100.0, Level = 3 }} + Level3List = new List() {new Level3() {Property1 = 3.0, Property2 = 100.0, Level = 3}} }; - + yield return new Level1() { Id = 1, @@ -95,15 +165,38 @@ private IEnumerable GetSampleDataWith3Nesting() { new Level2() { - Id = 101, Name = "Level2_1", ChildProp = subLvl2 , Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 } } + Id = 101, Name = "Level2_1", Level3List = new List() {new Level3() {Property1 = 2.0, Property2 = 100.0, Level = 0}} }, new Level2() { - Id = 102, Name = "Level2_2", ChildProp = new ChildProp() ,Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 200.0, Level = 0 } } + Id = 102, Name = "Level2_2", Level3List = new List() {new Level3() {Property1 = 3.0, Property2 = 200.0, Level = 1}} }, new Level2() { - Id = 103, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 300.0, Level = 0 } } + Id = 103, Name = "Level2_3", Level3List = new List() {new Level3() {Property1 = 4.0, Property2 = 300.0, Level = 2}} + } + } + }; + + yield return new Level1() + { + Id = 1, + Name = "Level1Name", + Level2List = new List() + { + new Level2() + { + Id = 101, Name = "Level2_1", ChildProp = subLvl2, + Level3List = new List() {new Level3() {Property1 = 2.0, Property2 = 100.0, Level = 0}} + }, + new Level2() + { + Id = 102, Name = "Level2_2", ChildProp = new ChildProp(), + Level3List = new List() {new Level3() {Property1 = 3.0, Property2 = 200.0, Level = 0}} + }, + new Level2() + { + Id = 103, Name = "Level2_3", Level3List = new List() {new Level3() {Property1 = 4.0, Property2 = 300.0, Level = 0}} } } }; @@ -115,15 +208,17 @@ private IEnumerable GetSampleDataWith3Nesting() { new Level2() { - Id = 101, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 } } + Id = 108, Name = "Level2_1", Level3List = new List() {new Level3() {Property1 = 4.0, Property2 = 100.0, Level = 0}} }, new Level2() { - Id = 102, Name = "Level2_2", ChildProp = new ChildProp(), Level3List = new List() { new Level3() { Property1 = 5.0, Property2 = 200.0, Level = 0 } } + Id = 109, Name = "Level2_2", ChildProp = new ChildProp(), + Level3List = new List() {new Level3() {Property1 = 5.0, Property2 = 200.0, Level = 0}} }, new Level2() { - Id = 103, Name = "Level2_3", ChildProp = subLvl2_2 ,Level3List = new List() { new Level3() { Property1 = 6.0, Property2 = 300.0, Level = 0 } } + Id = 110, Name = "Level2_3", ChildProp = subLvl2_2, + Level3List = new List() {new Level3() {Property1 = 6.0, Property2 = 300.0, Level = 0}} } } }; @@ -135,15 +230,15 @@ private IEnumerable GetSampleDataWith3Nesting() { new Level2() { - Id = 101, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 6.0, Property2 = 100.0, Level = 0 } } + Id = 111, Name = "Level2_1", Level3List = new List() {new Level3() {Property1 = 6.0, Property2 = 100.0, Level = 0}} }, new Level2() { - Id = 102, Name = "Level2_2", Level3List = new List() { new Level3() { Property1 = 7.0, Property2 = 200.0, Level = 0 } } + Id = 112, Name = "Level2_2", Level3List = new List() {new Level3() {Property1 = 7.0, Property2 = 200.0, Level = 0}} }, new Level2() { - Id = 103, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 8.0, Property2 = 300.0, Level = 0 } } + Id = 113, Name = "Level2_3", Level3List = new List() {new Level3() {Property1 = 8.0, Property2 = 300.0, Level = 0}} } } }; @@ -157,9 +252,9 @@ private IEnumerable GetSampleDataWith2Nestings() Name = "Level2Name", Level3List = new List() { - new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 3.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 } + new Level3() {Property1 = 2.0, Property2 = 100.0, Level = 0}, + new Level3() {Property1 = 3.0, Property2 = 100.0, Level = 0}, + new Level3() {Property1 = 4.0, Property2 = 100.0, Level = 0} } }; yield return new Level2() @@ -168,9 +263,9 @@ private IEnumerable GetSampleDataWith2Nestings() Name = "Level2Name2", Level3List = new List() { - new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 5.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 6.0, Property2 = 100.0, Level = 0 } + new Level3() {Property1 = 4.0, Property2 = 100.0, Level = 0}, + new Level3() {Property1 = 5.0, Property2 = 100.0, Level = 0}, + new Level3() {Property1 = 6.0, Property2 = 100.0, Level = 0} } }; } @@ -187,14 +282,15 @@ public class Level2 public int Id { get; set; } public string Name { get; set; } public List Level3List { get; set; } - + public ChildProp ChildProp { get; set; } } - + public class ChildProp { public List Level3List { get; set; } } + public class Level3 { public int Level { get; set; }