diff --git a/src/Ardalis.Specification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs b/src/Ardalis.Specification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs index ce8b0ba4..87925228 100644 --- a/src/Ardalis.Specification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs +++ b/src/Ardalis.Specification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs @@ -41,7 +41,6 @@ private IncludeEvaluator() { } /// public IQueryable GetQuery(IQueryable query, ISpecification specification) where T : class { - Type? previousReturnType = null; foreach (var includeExpression in specification.IncludeExpressions) { var lambdaExpr = includeExpression.LambdaExpression; @@ -49,14 +48,12 @@ public IQueryable GetQuery(IQueryable query, ISpecification specific if (includeExpression.Type == IncludeTypeEnum.Include) { var key = new CacheKey(typeof(T), lambdaExpr.ReturnType, null); - previousReturnType = lambdaExpr.ReturnType; var include = _cache.GetOrAdd(key, CreateIncludeDelegate); query = (IQueryable)include(query, lambdaExpr); } else if (includeExpression.Type == IncludeTypeEnum.ThenInclude) { - var key = new CacheKey(typeof(T), lambdaExpr.ReturnType, previousReturnType); - previousReturnType = lambdaExpr.ReturnType; + var key = new CacheKey(typeof(T), lambdaExpr.ReturnType, includeExpression.PreviousPropertyType); var include = _cache.GetOrAdd(key, CreateThenIncludeDelegate); query = (IQueryable)include(query, lambdaExpr); } @@ -104,7 +101,7 @@ private static Func CreateThenIncludeD private static bool IsGenericEnumerable(Type type, out Type propertyType) { - if (type.IsGenericType && typeof(IEnumerable).IsAssignableFrom(type)) + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { propertyType = type.GenericTypeArguments[0]; return true; diff --git a/src/Ardalis.Specification/Builders/Builder_Include.cs b/src/Ardalis.Specification/Builders/Builder_Include.cs index ee7ec96e..918ef314 100644 --- a/src/Ardalis.Specification/Builders/Builder_Include.cs +++ b/src/Ardalis.Specification/Builders/Builder_Include.cs @@ -101,7 +101,7 @@ public static IIncludableSpecificationBuilder Include Include ThenI { if (condition && !Specification.IsChainDiscarded) { - var expr = new IncludeExpressionInfo(navigationSelector, IncludeTypeEnum.ThenInclude); + var expr = new IncludeExpressionInfo(navigationSelector, typeof(TPreviousProperty)); builder.Specification.Add(expr); } else @@ -228,7 +228,7 @@ public static IIncludableSpecificationBuilder ThenInclude.IsChainDiscarded) { - var expr = new IncludeExpressionInfo(navigationSelector, IncludeTypeEnum.ThenInclude); + var expr = new IncludeExpressionInfo(navigationSelector, typeof(TPreviousProperty)); builder.Specification.Add(expr); } else @@ -275,7 +275,7 @@ public static IIncludableSpecificationBuilder ThenI { if (condition && !Specification.IsChainDiscarded) { - var expr = new IncludeExpressionInfo(navigationSelector, IncludeTypeEnum.ThenInclude); + var expr = new IncludeExpressionInfo(navigationSelector, typeof(IEnumerable)); builder.Specification.Add(expr); } else @@ -320,7 +320,7 @@ public static IIncludableSpecificationBuilder ThenInclude.IsChainDiscarded) { - var expr = new IncludeExpressionInfo(navigationSelector, IncludeTypeEnum.ThenInclude); + var expr = new IncludeExpressionInfo(navigationSelector, typeof(IEnumerable)); builder.Specification.Add(expr); } else diff --git a/src/Ardalis.Specification/Expressions/IncludeExpressionInfo.cs b/src/Ardalis.Specification/Expressions/IncludeExpressionInfo.cs index 0a5db08b..12816151 100644 --- a/src/Ardalis.Specification/Expressions/IncludeExpressionInfo.cs +++ b/src/Ardalis.Specification/Expressions/IncludeExpressionInfo.cs @@ -11,16 +11,32 @@ public class IncludeExpressionInfo /// public LambdaExpression LambdaExpression { get; } + /// + /// The type of the previously included entity. + /// + public Type? PreviousPropertyType { get; } + /// /// The include type. /// public IncludeTypeEnum Type { get; } - public IncludeExpressionInfo(LambdaExpression expression, IncludeTypeEnum includeType) + public IncludeExpressionInfo(LambdaExpression expression) + { + _ = expression ?? throw new ArgumentNullException(nameof(expression)); + + LambdaExpression = expression; + PreviousPropertyType = null; + Type = IncludeTypeEnum.Include; + } + + public IncludeExpressionInfo(LambdaExpression expression, Type previousPropertyType) { _ = expression ?? throw new ArgumentNullException(nameof(expression)); + _ = previousPropertyType ?? throw new ArgumentNullException(nameof(previousPropertyType)); LambdaExpression = expression; - Type = includeType; + PreviousPropertyType = previousPropertyType; + Type = IncludeTypeEnum.ThenInclude; } } diff --git a/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Evaluators/IncludeEvaluatorTests.cs b/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Evaluators/IncludeEvaluatorTests.cs index f13f02f2..57c5e90b 100644 --- a/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Evaluators/IncludeEvaluatorTests.cs +++ b/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Evaluators/IncludeEvaluatorTests.cs @@ -35,7 +35,7 @@ public void QueriesMatch_GivenInheritanceModel() var spec = new Specification(); spec.Query .Include(x => x.BarChildren) - .ThenInclude(x => (x as BarDerived)!.BarDerivedInfo); + .ThenInclude(x => (x as BarDerived)!.BarDerivedInfo); var actual = _evaluator .GetQuery(DbContext.Bars, spec) diff --git a/tests/Ardalis.Specification.Tests/Expressions/IncludeExpressionInfoTests.cs b/tests/Ardalis.Specification.Tests/Expressions/IncludeExpressionInfoTests.cs index 9ecab725..949ac89a 100644 --- a/tests/Ardalis.Specification.Tests/Expressions/IncludeExpressionInfoTests.cs +++ b/tests/Ardalis.Specification.Tests/Expressions/IncludeExpressionInfoTests.cs @@ -9,18 +9,44 @@ public record City(int Id); [Fact] public void Constructor_ThrowsArgumentNullException_GivenNullForLambdaExpression() { - var sut = () => new IncludeExpressionInfo(null!, IncludeTypeEnum.Include); + var sut = () => new IncludeExpressionInfo(null!); sut.Should().Throw().WithParameterName("expression"); + + + sut = () => new IncludeExpressionInfo(null!, typeof(Customer)); + + sut.Should().Throw().WithParameterName("expression"); + } + + [Fact] + public void Constructor_ThrowsArgumentNullException_GivenNullForPreviousPropertyType() + { + Expression> expr = x => x.Address; + var sut = () => new IncludeExpressionInfo(expr, null!); + + sut.Should().Throw().WithParameterName("previousPropertyType"); } [Fact] public void Constructor_GivenIncludeExpression() { Expression> expr = x => x.Address; - var sut = new IncludeExpressionInfo(expr, IncludeTypeEnum.Include); + var sut = new IncludeExpressionInfo(expr); sut.Type.Should().Be(IncludeTypeEnum.Include); sut.LambdaExpression.Should().Be(expr); } + + [Fact] + public void Constructor_GivenThenIncludeExpressionAndPreviousPropertyType() + { + Expression> expr = x => x.City; + var previousPropertyType = typeof(Customer); + var sut = new IncludeExpressionInfo(expr, previousPropertyType); + + sut.Type.Should().Be(IncludeTypeEnum.ThenInclude); + sut.LambdaExpression.Should().Be(expr); + sut.PreviousPropertyType.Should().Be(previousPropertyType); + } }