From 679596c227667199572a1f9d73338e9c383708cd Mon Sep 17 00:00:00 2001 From: Fati Iseni Date: Tue, 3 Jun 2025 17:21:23 +0200 Subject: [PATCH] Added IgnoreAutoIncludes --- .../Evaluators/IgnoreAutoIncludesEvaluator.cs | 25 ++++++++ .../Evaluators/SpecificationEvaluator.cs | 1 + .../Builders/Builder_Flags.cs | 58 +++++++++++++++++++ src/QuerySpecification/Internals/SpecFlags.cs | 3 +- src/QuerySpecification/Specification.cs | 5 ++ .../IgnoreAutoIncludesEvaluatorTests.cs | 41 +++++++++++++ .../Evaluators/SpecificationEvaluatorTests.cs | 9 ++- .../Builders/Builder_IgnoreAutoIncludes.cs | 46 +++++++++++++++ 8 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 src/QuerySpecification.EntityFrameworkCore/Evaluators/IgnoreAutoIncludesEvaluator.cs create mode 100644 tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/IgnoreAutoIncludesEvaluatorTests.cs create mode 100644 tests/QuerySpecification.Tests/Builders/Builder_IgnoreAutoIncludes.cs diff --git a/src/QuerySpecification.EntityFrameworkCore/Evaluators/IgnoreAutoIncludesEvaluator.cs b/src/QuerySpecification.EntityFrameworkCore/Evaluators/IgnoreAutoIncludesEvaluator.cs new file mode 100644 index 00000000..3d528746 --- /dev/null +++ b/src/QuerySpecification.EntityFrameworkCore/Evaluators/IgnoreAutoIncludesEvaluator.cs @@ -0,0 +1,25 @@ +namespace Pozitron.QuerySpecification; + +/// +/// Evaluator to apply IgnoreAutoIncludes to the query if the specification has IgnoreAutoIncludes set to true. +/// +public sealed class IgnoreAutoIncludesEvaluator : IEvaluator +{ + + /// + /// Gets the singleton instance of the class. + /// + public static IgnoreAutoIncludesEvaluator Instance = new(); + private IgnoreAutoIncludesEvaluator() { } + + /// + public IQueryable Evaluate(IQueryable source, Specification specification) where T : class + { + if (specification.IgnoreAutoIncludes) + { + source = source.IgnoreAutoIncludes(); + } + + return source; + } +} diff --git a/src/QuerySpecification.EntityFrameworkCore/Evaluators/SpecificationEvaluator.cs b/src/QuerySpecification.EntityFrameworkCore/Evaluators/SpecificationEvaluator.cs index 384db854..9b9590ae 100644 --- a/src/QuerySpecification.EntityFrameworkCore/Evaluators/SpecificationEvaluator.cs +++ b/src/QuerySpecification.EntityFrameworkCore/Evaluators/SpecificationEvaluator.cs @@ -32,6 +32,7 @@ public SpecificationEvaluator() AsNoTrackingWithIdentityResolutionEvaluator.Instance, AsTrackingEvaluator.Instance, AsSplitQueryEvaluator.Instance, + IgnoreAutoIncludesEvaluator.Instance, ]; } diff --git a/src/QuerySpecification/Builders/Builder_Flags.cs b/src/QuerySpecification/Builders/Builder_Flags.cs index abd66420..f1139671 100644 --- a/src/QuerySpecification/Builders/Builder_Flags.cs +++ b/src/QuerySpecification/Builders/Builder_Flags.cs @@ -63,6 +63,64 @@ public static ISpecificationBuilder IgnoreQueryFilters( return builder; } + /// + /// Configures the specification to ignore auto includes. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreAutoIncludes( + this ISpecificationBuilder builder) where T : class + => IgnoreAutoIncludes(builder, true); + + /// + /// Configures the specification to ignore auto includes if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreAutoIncludes( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AddOrUpdateFlag(SpecFlags.IgnoreAutoIncludes, true); + } + return builder; + } + + /// + /// Configures the specification to ignore auto includes. + /// + /// The type of the entity. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreAutoIncludes( + this ISpecificationBuilder builder) where T : class + => IgnoreAutoIncludes(builder, true); + + /// + /// Configures the specification to ignore auto includes if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreAutoIncludes( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AddOrUpdateFlag(SpecFlags.IgnoreAutoIncludes, true); + } + return builder; + } + /// /// Configures the specification to use split queries. /// diff --git a/src/QuerySpecification/Internals/SpecFlags.cs b/src/QuerySpecification/Internals/SpecFlags.cs index 17cf9232..11803e43 100644 --- a/src/QuerySpecification/Internals/SpecFlags.cs +++ b/src/QuerySpecification/Internals/SpecFlags.cs @@ -7,5 +7,6 @@ internal enum SpecFlags AsNoTracking = 2, AsNoTrackingWithIdentityResolution = 4, AsTracking = 8, - AsSplitQuery = 16 + AsSplitQuery = 16, + IgnoreAutoIncludes = 32, } diff --git a/src/QuerySpecification/Specification.cs b/src/QuerySpecification/Specification.cs index 5040a3f9..014e4bc0 100644 --- a/src/QuerySpecification/Specification.cs +++ b/src/QuerySpecification/Specification.cs @@ -191,6 +191,11 @@ public virtual bool IsSatisfiedBy(T entity) /// public bool IgnoreQueryFilters => GetFlag(SpecFlags.IgnoreQueryFilters); + /// + /// Gets a value indicating whether IgnoreAutoIncludes is applied. + /// + public bool IgnoreAutoIncludes => GetFlag(SpecFlags.IgnoreAutoIncludes); + /// /// Gets a value indicating whether AsSplitQuery is applied. /// diff --git a/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/IgnoreAutoIncludesEvaluatorTests.cs b/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/IgnoreAutoIncludesEvaluatorTests.cs new file mode 100644 index 00000000..8e023777 --- /dev/null +++ b/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/IgnoreAutoIncludesEvaluatorTests.cs @@ -0,0 +1,41 @@ +namespace Tests.Evaluators; + +[Collection("SharedCollection")] +public class IgnoreAutoIncludesEvaluatorTests(TestFactory factory) : IntegrationTest(factory) +{ + private static readonly IgnoreAutoIncludesEvaluator _evaluator = IgnoreAutoIncludesEvaluator.Instance; + + [Fact] + public void QueriesMatch_GivenIgnoreAutoIncludes() + { + var spec = new Specification(); + spec.Query.IgnoreAutoIncludes(); + + var actual = _evaluator.Evaluate(DbContext.Countries, spec) + .ToQueryString(); + + var expected = DbContext.Countries + .IgnoreAutoIncludes() + .ToQueryString(); + + actual.Should().Be(expected); + } + + [Fact] + public void Applies_GivenIgnoreAutoIncludes() + { + var spec = new Specification(); + spec.Query.IgnoreAutoIncludes(); + + var actual = _evaluator.Evaluate(DbContext.Countries, spec) + .Expression + .ToString(); + + var expected = DbContext.Countries + .IgnoreAutoIncludes() + .Expression + .ToString(); + + actual.Should().Be(expected); + } +} diff --git a/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/SpecificationEvaluatorTests.cs b/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/SpecificationEvaluatorTests.cs index 4a1c3e6d..f55c6ea2 100644 --- a/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/SpecificationEvaluatorTests.cs +++ b/tests/QuerySpecification.EntityFrameworkCore.Tests/Evaluators/SpecificationEvaluatorTests.cs @@ -341,7 +341,8 @@ public void GivenSpecWithMultipleFlags() .AsTracking() .AsNoTrackingWithIdentityResolution() .AsNoTracking() // the last one overwrites other Tracking behavior. - .AsSplitQuery(); + .AsSplitQuery() + .IgnoreAutoIncludes(); var actual = _evaluator.Evaluate(DbContext.Countries, spec) .Expression @@ -351,6 +352,7 @@ public void GivenSpecWithMultipleFlags() .IgnoreQueryFilters() .AsNoTracking() .AsSplitQuery() + .IgnoreAutoIncludes() .Expression .ToString(); @@ -380,7 +382,7 @@ public void DerivedSpecificationEvaluatorCanAlterDefaultEvaluator() var evaluator = new SpecificationEvaluatorDerived(); var result = EvaluatorsOf(evaluator); - result.Should().HaveCount(12); + result.Should().HaveCount(13); result[0].Should().BeOfType(); result[1].Should().BeOfType(); result[2].Should().BeOfType(); @@ -392,7 +394,8 @@ public void DerivedSpecificationEvaluatorCanAlterDefaultEvaluator() result[8].Should().BeOfType(); result[9].Should().BeOfType(); result[10].Should().BeOfType(); - result[11].Should().BeOfType(); + result[11].Should().BeOfType(); + result[12].Should().BeOfType(); } private class SpecificationEvaluatorDerived : SpecificationEvaluator diff --git a/tests/QuerySpecification.Tests/Builders/Builder_IgnoreAutoIncludes.cs b/tests/QuerySpecification.Tests/Builders/Builder_IgnoreAutoIncludes.cs new file mode 100644 index 00000000..21494ec8 --- /dev/null +++ b/tests/QuerySpecification.Tests/Builders/Builder_IgnoreAutoIncludes.cs @@ -0,0 +1,46 @@ +namespace Tests.Builders; + +public class Builder_IgnoreAutoIncludes +{ + public record Customer(int Id, string Name); + + [Fact] + public void DoesNothing_GivenNoIgnoreAutoIncludes() + { + var spec1 = new Specification(); + var spec2 = new Specification(); + + spec1.IgnoreAutoIncludes.Should().Be(false); + spec2.IgnoreAutoIncludes.Should().Be(false); + } + + [Fact] + public void DoesNothing_GivenIgnoreAutoIncludesWithFalseCondition() + { + var spec1 = new Specification(); + spec1.Query + .IgnoreAutoIncludes(false); + + var spec2 = new Specification(); + spec2.Query + .IgnoreAutoIncludes(false); + + spec1.IgnoreAutoIncludes.Should().Be(false); + spec2.IgnoreAutoIncludes.Should().Be(false); + } + + [Fact] + public void SetsIgnoreAutoIncludes_GivenIgnoreAutoIncludes() + { + var spec1 = new Specification(); + spec1.Query + .IgnoreAutoIncludes(); + + var spec2 = new Specification(); + spec2.Query + .IgnoreAutoIncludes(); + + spec1.IgnoreAutoIncludes.Should().Be(true); + spec2.IgnoreAutoIncludes.Should().Be(true); + } +}