Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Pozitron.QuerySpecification;

/// <summary>
/// Evaluator to apply IgnoreAutoIncludes to the query if the specification has IgnoreAutoIncludes set to true.
/// </summary>
public sealed class IgnoreAutoIncludesEvaluator : IEvaluator
{

/// <summary>
/// Gets the singleton instance of the <see cref="IgnoreAutoIncludesEvaluator"/> class.
/// </summary>
public static IgnoreAutoIncludesEvaluator Instance = new();
private IgnoreAutoIncludesEvaluator() { }

/// <inheritdoc/>
public IQueryable<T> Evaluate<T>(IQueryable<T> source, Specification<T> specification) where T : class
{
if (specification.IgnoreAutoIncludes)
{
source = source.IgnoreAutoIncludes();
}

return source;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public SpecificationEvaluator()
AsNoTrackingWithIdentityResolutionEvaluator.Instance,
AsTrackingEvaluator.Instance,
AsSplitQueryEvaluator.Instance,
IgnoreAutoIncludesEvaluator.Instance,
];
}

Expand Down
58 changes: 58 additions & 0 deletions src/QuerySpecification/Builders/Builder_Flags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,64 @@ public static ISpecificationBuilder<T> IgnoreQueryFilters<T>(
return builder;
}

/// <summary>
/// Configures the specification to ignore auto includes.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T, TResult> IgnoreAutoIncludes<T, TResult>(
this ISpecificationBuilder<T, TResult> builder) where T : class
=> IgnoreAutoIncludes(builder, true);

/// <summary>
/// Configures the specification to ignore auto includes if the condition is true.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <param name="condition">The condition to evaluate.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T, TResult> IgnoreAutoIncludes<T, TResult>(
this ISpecificationBuilder<T, TResult> builder,
bool condition) where T : class
{
if (condition)
{
builder.Specification.AddOrUpdateFlag(SpecFlags.IgnoreAutoIncludes, true);
}
return builder;
}

/// <summary>
/// Configures the specification to ignore auto includes.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T> IgnoreAutoIncludes<T>(
this ISpecificationBuilder<T> builder) where T : class
=> IgnoreAutoIncludes(builder, true);

/// <summary>
/// Configures the specification to ignore auto includes if the condition is true.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <param name="condition">The condition to evaluate.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T> IgnoreAutoIncludes<T>(
this ISpecificationBuilder<T> builder,
bool condition) where T : class
{
if (condition)
{
builder.Specification.AddOrUpdateFlag(SpecFlags.IgnoreAutoIncludes, true);
}
return builder;
}

/// <summary>
/// Configures the specification to use split queries.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/QuerySpecification/Internals/SpecFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ internal enum SpecFlags
AsNoTracking = 2,
AsNoTrackingWithIdentityResolution = 4,
AsTracking = 8,
AsSplitQuery = 16
AsSplitQuery = 16,
IgnoreAutoIncludes = 32,
}
5 changes: 5 additions & 0 deletions src/QuerySpecification/Specification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ public virtual bool IsSatisfiedBy(T entity)
/// </summary>
public bool IgnoreQueryFilters => GetFlag(SpecFlags.IgnoreQueryFilters);

/// <summary>
/// Gets a value indicating whether IgnoreAutoIncludes is applied.
/// </summary>
public bool IgnoreAutoIncludes => GetFlag(SpecFlags.IgnoreAutoIncludes);

/// <summary>
/// Gets a value indicating whether AsSplitQuery is applied.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Country>();
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<Country>();
spec.Query.IgnoreAutoIncludes();

var actual = _evaluator.Evaluate(DbContext.Countries, spec)
.Expression
.ToString();

var expected = DbContext.Countries
.IgnoreAutoIncludes()
.Expression
.ToString();

actual.Should().Be(expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -351,6 +352,7 @@ public void GivenSpecWithMultipleFlags()
.IgnoreQueryFilters()
.AsNoTracking()
.AsSplitQuery()
.IgnoreAutoIncludes()
.Expression
.ToString();

Expand Down Expand Up @@ -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<LikeEvaluator>();
result[1].Should().BeOfType<WhereEvaluator>();
result[2].Should().BeOfType<LikeEvaluator>();
Expand All @@ -392,7 +394,8 @@ public void DerivedSpecificationEvaluatorCanAlterDefaultEvaluator()
result[8].Should().BeOfType<AsNoTrackingWithIdentityResolutionEvaluator>();
result[9].Should().BeOfType<AsTrackingEvaluator>();
result[10].Should().BeOfType<AsSplitQueryEvaluator>();
result[11].Should().BeOfType<WhereEvaluator>();
result[11].Should().BeOfType<IgnoreAutoIncludesEvaluator>();
result[12].Should().BeOfType<WhereEvaluator>();
}

private class SpecificationEvaluatorDerived : SpecificationEvaluator
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Customer>();
var spec2 = new Specification<Customer, string>();

spec1.IgnoreAutoIncludes.Should().Be(false);
spec2.IgnoreAutoIncludes.Should().Be(false);
}

[Fact]
public void DoesNothing_GivenIgnoreAutoIncludesWithFalseCondition()
{
var spec1 = new Specification<Customer>();
spec1.Query
.IgnoreAutoIncludes(false);

var spec2 = new Specification<Customer, string>();
spec2.Query
.IgnoreAutoIncludes(false);

spec1.IgnoreAutoIncludes.Should().Be(false);
spec2.IgnoreAutoIncludes.Should().Be(false);
}

[Fact]
public void SetsIgnoreAutoIncludes_GivenIgnoreAutoIncludes()
{
var spec1 = new Specification<Customer>();
spec1.Query
.IgnoreAutoIncludes();

var spec2 = new Specification<Customer, string>();
spec2.Query
.IgnoreAutoIncludes();

spec1.IgnoreAutoIncludes.Should().Be(true);
spec2.IgnoreAutoIncludes.Should().Be(true);
}
}