Skip to content

Commit 50ec01a

Browse files
authored
Refactor the state of include expressions as OneOrMany. (#510)
1 parent a01c57e commit 50ec01a

File tree

5 files changed

+81
-13
lines changed

5 files changed

+81
-13
lines changed

src/Ardalis.Specification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.EntityFrameworkCore.Query;
2-
using System.Collections;
32
using System.Collections.Concurrent;
43
using System.Diagnostics;
54
using System.Reflection;
@@ -41,6 +40,18 @@ private IncludeEvaluator() { }
4140
/// <inheritdoc/>
4241
public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
4342
{
43+
if (specification is Specification<T> spec)
44+
{
45+
if (spec.OneOrManyIncludeExpressions.IsEmpty) return query;
46+
if (spec.OneOrManyIncludeExpressions.SingleOrDefault is { } includeExpression)
47+
{
48+
var lambdaExpr = includeExpression.LambdaExpression;
49+
var key = new CacheKey(typeof(T), lambdaExpr.ReturnType, null);
50+
var include = _cache.GetOrAdd(key, CreateIncludeDelegate);
51+
return (IQueryable<T>)include(query, lambdaExpr);
52+
}
53+
}
54+
4455
foreach (var includeExpression in specification.IncludeExpressions)
4556
{
4657
var lambdaExpr = includeExpression.LambdaExpression;

src/Ardalis.Specification.EntityFrameworkCore/Evaluators/IncludeStringEvaluator.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ private IncludeStringEvaluator() { }
1010
/// <inheritdoc/>
1111
public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
1212
{
13+
if (specification is Specification<T> spec)
14+
{
15+
if (spec.OneOrManyIncludeStrings.IsEmpty) return query;
16+
if (spec.OneOrManyIncludeStrings.SingleOrDefault is { } includeString)
17+
{
18+
return query.Include(includeString);
19+
}
20+
}
21+
1322
foreach (var includeString in specification.IncludeStrings)
1423
{
1524
query = query.Include(includeString);

src/Ardalis.Specification/Specification.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ public class Specification<T, TResult> : Specification<T>, ISpecification<T, TRe
2727
public class Specification<T> : ISpecification<T>
2828
{
2929
private const int DEFAULT_CAPACITY_SEARCH = 2;
30-
private const int DEFAULT_CAPACITY_INCLUDE = 2;
31-
private const int DEFAULT_CAPACITY_INCLUDESTRING = 1;
3230

3331
// It is utilized only during the building stage for the sub-chains. Once the state is built, we don't care about it anymore.
3432
// The initial value is not important since the value is always initialized by the root of the chain.
@@ -43,8 +41,8 @@ public class Specification<T> : ISpecification<T>
4341
private OneOrMany<WhereExpressionInfo<T>> _whereExpressions = new();
4442
private List<SearchExpressionInfo<T>>? _searchExpressions;
4543
private OneOrMany<OrderExpressionInfo<T>> _orderExpressions = new();
46-
private List<IncludeExpressionInfo>? _includeExpressions;
47-
private List<string>? _includeStrings;
44+
private OneOrMany<IncludeExpressionInfo> _includeExpressions = new();
45+
private OneOrMany<string> _includeStrings = new();
4846
private Dictionary<string, object>? _items;
4947
private OneOrMany<string> _queryTags = new();
5048

@@ -94,8 +92,8 @@ public class Specification<T> : ISpecification<T>
9492
// Specs are not intended to be thread-safe, so we don't need to worry about thread-safety here.
9593
internal void Add(WhereExpressionInfo<T> whereExpression) => _whereExpressions.Add(whereExpression);
9694
internal void Add(OrderExpressionInfo<T> orderExpression) => _orderExpressions.Add(orderExpression);
97-
internal void Add(IncludeExpressionInfo includeExpression) => (_includeExpressions ??= new(DEFAULT_CAPACITY_INCLUDE)).Add(includeExpression);
98-
internal void Add(string includeString) => (_includeStrings ??= new(DEFAULT_CAPACITY_INCLUDESTRING)).Add(includeString);
95+
internal void Add(IncludeExpressionInfo includeExpression) => _includeExpressions.Add(includeExpression);
96+
internal void Add(string includeString) => _includeStrings.Add(includeString);
9997
internal void Add(SearchExpressionInfo<T> searchExpression)
10098
{
10199
if (_searchExpressions is null)
@@ -132,16 +130,18 @@ internal void Add(SearchExpressionInfo<T> searchExpression)
132130
public IEnumerable<OrderExpressionInfo<T>> OrderExpressions => _orderExpressions.Values;
133131

134132
/// <inheritdoc/>
135-
public IEnumerable<IncludeExpressionInfo> IncludeExpressions => _includeExpressions ?? Enumerable.Empty<IncludeExpressionInfo>();
133+
public IEnumerable<IncludeExpressionInfo> IncludeExpressions => _includeExpressions.Values;
136134

137135
/// <inheritdoc/>
138-
public IEnumerable<string> IncludeStrings => _includeStrings ?? Enumerable.Empty<string>();
136+
public IEnumerable<string> IncludeStrings => _includeStrings.Values;
139137

140138
/// <inheritdoc/>
141139
public IEnumerable<string> QueryTags => _queryTags.Values;
142140

143141
internal OneOrMany<WhereExpressionInfo<T>> OneOrManyWhereExpressions => _whereExpressions;
144142
internal OneOrMany<OrderExpressionInfo<T>> OneOrManyOrderExpressions => _orderExpressions;
143+
internal OneOrMany<IncludeExpressionInfo> OneOrManyIncludeExpressions => _includeExpressions;
144+
internal OneOrMany<string> OneOrManyIncludeStrings => _includeStrings;
145145
internal OneOrMany<string> OneOrManyQueryTags => _queryTags;
146146

147147
/// <inheritdoc/>
@@ -179,14 +179,14 @@ void ISpecification<T>.CopyTo(Specification<T> otherSpec)
179179
otherSpec._whereExpressions = _whereExpressions.Clone();
180180
}
181181

182-
if (_includeExpressions is not null)
182+
if (!_includeExpressions.IsEmpty)
183183
{
184-
otherSpec._includeExpressions = _includeExpressions.ToList();
184+
otherSpec._includeExpressions = _includeExpressions.Clone();
185185
}
186186

187-
if (_includeStrings is not null)
187+
if (!_includeStrings.IsEmpty)
188188
{
189-
otherSpec._includeStrings = _includeStrings.ToList();
189+
otherSpec._includeStrings = _includeStrings.Clone();
190190
}
191191

192192
if (!_orderExpressions.IsEmpty)

tests/Ardalis.Specification.EntityFrameworkCore.Tests/Evaluators/IncludeEvaluatorTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ public class IncludeEvaluatorTests(TestFactory factory) : IntegrationTest(factor
55
{
66
private static readonly IncludeEvaluator _evaluator = IncludeEvaluator.Instance;
77

8+
[Fact]
9+
public void QueriesMatch_GivenNoIncludeExpression()
10+
{
11+
var spec = new Specification<Store>();
12+
13+
var actual = _evaluator
14+
.GetQuery(DbContext.Stores, spec)
15+
.ToQueryString();
16+
17+
var expected = DbContext.Stores
18+
.ToQueryString();
19+
20+
actual.Should().Be(expected);
21+
}
22+
23+
[Fact]
24+
public void QueriesMatch_GivenSingleIncludeExpression()
25+
{
26+
var spec = new Specification<Store>();
27+
spec.Query
28+
.Include(x => x.Products.Where(x => x.Id > 10));
29+
30+
var actual = _evaluator
31+
.GetQuery(DbContext.Stores, spec)
32+
.ToQueryString();
33+
34+
var expected = DbContext.Stores
35+
.Include(x => x.Products.Where(x => x.Id > 10))
36+
.ToQueryString();
37+
38+
actual.Should().Be(expected);
39+
}
40+
841
[Fact]
942
public void QueriesMatch_GivenIncludeExpressions()
1043
{

tests/Ardalis.Specification.EntityFrameworkCore.Tests/Evaluators/IncludeStringEvaluatorTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ public class IncludeStringEvaluatorTests(TestFactory factory) : IntegrationTest(
55
{
66
private static readonly IncludeStringEvaluator _evaluator = IncludeStringEvaluator.Instance;
77

8+
[Fact]
9+
public void QueriesMatch_GivenNoIncludeString()
10+
{
11+
var spec = new Specification<Store>();
12+
13+
var actual = _evaluator
14+
.GetQuery(DbContext.Stores, spec)
15+
.ToQueryString();
16+
17+
var expected = DbContext.Stores
18+
.ToQueryString();
19+
20+
actual.Should().Be(expected);
21+
}
22+
823
[Fact]
924
public void QueriesMatch_GivenIncludeString()
1025
{

0 commit comments

Comments
 (0)