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);
+ }
+}