Skip to content

Commit f37fb7e

Browse files
authored
Refactor WithProjectionOf and internals. (#517)
1 parent fc9b0a4 commit f37fb7e

File tree

7 files changed

+147
-65
lines changed

7 files changed

+147
-65
lines changed

src/Ardalis.Specification/ISpecification.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,4 @@ public interface ISpecification<T>
157157
/// <param name="entity">The entity to be validated</param>
158158
/// <returns></returns>
159159
bool IsSatisfiedBy(T entity);
160-
161-
internal void CopyTo(Specification<T> otherSpec);
162160
}

src/Ardalis.Specification/Specification.cs

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -139,55 +139,41 @@ public virtual bool IsSatisfiedBy(T entity)
139139
return validator.IsValid(entity, this);
140140
}
141141

142-
void ISpecification<T>.CopyTo(Specification<T> otherSpec)
142+
internal Specification<T> Clone()
143143
{
144-
otherSpec.PostProcessingAction = PostProcessingAction;
145-
otherSpec.CacheKey = CacheKey;
146-
otherSpec.Take = Take;
147-
otherSpec.Skip = Skip;
148-
otherSpec.IgnoreQueryFilters = IgnoreQueryFilters;
149-
otherSpec.IgnoreAutoIncludes = IgnoreAutoIncludes;
150-
otherSpec.AsSplitQuery = AsSplitQuery;
151-
otherSpec.AsNoTracking = AsNoTracking;
152-
otherSpec.AsTracking = AsTracking;
153-
otherSpec.AsNoTrackingWithIdentityResolution = AsNoTrackingWithIdentityResolution;
154-
155-
// The expression containers are immutable, having the same instance is fine.
156-
// We'll just create new collections.
157-
158-
if (!_whereExpressions.IsEmpty)
159-
{
160-
otherSpec._whereExpressions = _whereExpressions.Clone();
161-
}
162-
163-
if (!_includeExpressions.IsEmpty)
164-
{
165-
otherSpec._includeExpressions = _includeExpressions.Clone();
166-
}
167-
168-
if (!_includeStrings.IsEmpty)
169-
{
170-
otherSpec._includeStrings = _includeStrings.Clone();
171-
}
172-
173-
if (!_orderExpressions.IsEmpty)
174-
{
175-
otherSpec._orderExpressions = _orderExpressions.Clone();
176-
}
177-
178-
if (!_searchExpressions.IsEmpty)
179-
{
180-
otherSpec._searchExpressions = _searchExpressions.Clone();
181-
}
144+
var newSpec = new Specification<T>();
145+
CopyState(this, newSpec);
146+
return newSpec;
147+
}
182148

183-
if (!_queryTags.IsEmpty)
184-
{
185-
otherSpec._queryTags = _queryTags.Clone();
186-
}
149+
internal Specification<T, TResult> Clone<TResult>()
150+
{
151+
var newSpec = new Specification<T, TResult>();
152+
CopyState(this, newSpec);
153+
return newSpec;
154+
}
187155

188-
if (_items is not null)
156+
private static void CopyState(Specification<T> source, Specification<T> target)
157+
{
158+
target.PostProcessingAction = source.PostProcessingAction;
159+
target.CacheKey = source.CacheKey;
160+
target.Take = source.Take;
161+
target.Skip = source.Skip;
162+
target.IgnoreAutoIncludes = source.IgnoreAutoIncludes;
163+
target.IgnoreQueryFilters = source.IgnoreQueryFilters;
164+
target.AsSplitQuery = source.AsSplitQuery;
165+
target.AsNoTracking = source.AsNoTracking;
166+
target.AsTracking = source.AsTracking;
167+
target.AsNoTrackingWithIdentityResolution = source.AsNoTrackingWithIdentityResolution;
168+
target._whereExpressions = source._whereExpressions.Clone();
169+
target._searchExpressions = source._searchExpressions.Clone();
170+
target._orderExpressions = source._orderExpressions.Clone();
171+
target._includeExpressions = source._includeExpressions.Clone();
172+
target._includeStrings = source._includeStrings.Clone();
173+
target._queryTags = source._queryTags.Clone();
174+
if (source._items is not null)
189175
{
190-
otherSpec._items = new Dictionary<string, object>(_items);
176+
target._items = new Dictionary<string, object>(source._items);
191177
}
192178
}
193179
}

src/Ardalis.Specification/SpecificationExtensions.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ public static class SpecificationExtensions
1717
/// <see langword="null"/>.</param>
1818
/// <returns>A new <see cref="Specification{T, TResult}"/> that represents the result of applying the projection to the
1919
/// source specification.</returns>
20-
public static Specification<T, TResult> WithProjectionOf<T, TResult>(this ISpecification<T> source, ISpecification<T, TResult> projectionSpec)
20+
public static Specification<T, TResult> WithProjectionOf<T, TResult>(this Specification<T> source, Specification<T, TResult> projectionSpec)
2121
{
22-
var newSpec = new Specification<T, TResult>();
23-
source.CopyTo(newSpec);
22+
var newSpec = source.Clone<TResult>();
2423
newSpec.Selector = projectionSpec.Selector;
2524
newSpec.SelectorMany = projectionSpec.SelectorMany;
2625
newSpec.PostProcessingAction = projectionSpec.PostProcessingAction;

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,5 @@ public IEnumerable<T> Evaluate(IEnumerable<T> entities)
9696
=> throw new NotImplementedException();
9797
public bool IsSatisfiedBy(T entity)
9898
=> throw new NotImplementedException();
99-
100-
void ISpecification<T>.CopyTo(Specification<T> otherSpec)
101-
{
102-
throw new NotImplementedException();
103-
}
10499
}
105100
}

tests/Ardalis.Specification.Tests/Evaluators/SearchMemoryEvaluatorCustomSpecTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,5 @@ public IEnumerable<T> Evaluate(IEnumerable<T> entities)
194194
=> throw new NotImplementedException();
195195
public bool IsSatisfiedBy(T entity)
196196
=> throw new NotImplementedException();
197-
198-
void ISpecification<T>.CopyTo(Specification<T> otherSpec)
199-
{
200-
throw new NotImplementedException();
201-
}
202197
}
203198
}

tests/Ardalis.Specification.Tests/SpecificationTests.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,120 @@ public void CollectionsProperties_ReturnEmptyEnumerable_GivenEmptySpec()
1818
spec.IncludeStrings.Should().BeSameAs(Enumerable.Empty<string>());
1919
}
2020

21+
[Fact]
22+
public void Clone_ReturnsCopy()
23+
{
24+
var spec = new Specification<Customer>();
25+
spec.Items.Add("test", "test");
26+
spec.Query
27+
.Where(x => x.Name == "test")
28+
.Include(x => x.Address)
29+
.Include("Address")
30+
.OrderBy(x => x.Id)
31+
.Search(x => x.Name, "test")
32+
.Take(2)
33+
.Skip(3)
34+
.WithCacheKey("testKey")
35+
.IgnoreQueryFilters()
36+
.IgnoreQueryFilters()
37+
.AsSplitQuery()
38+
.AsNoTracking()
39+
.TagWith("testQuery1")
40+
.PostProcessingAction(x => x.Where(x => x.Id > 0));
41+
42+
var newSpec = spec.Clone();
43+
44+
newSpec.Should().BeOfType<Specification<Customer>>();
45+
46+
newSpec.Items.Should().NotBeSameAs(spec.Items);
47+
newSpec.Items.Should().BeEquivalentTo(spec.Items);
48+
49+
newSpec.WhereExpressions.Should().NotBeSameAs(spec.WhereExpressions);
50+
newSpec.WhereExpressions.Should().Equal(spec.WhereExpressions);
51+
52+
newSpec.IncludeExpressions.Should().NotBeSameAs(spec.IncludeExpressions);
53+
newSpec.IncludeExpressions.Should().Equal(spec.IncludeExpressions);
54+
55+
newSpec.IncludeStrings.Should().NotBeSameAs(spec.IncludeStrings);
56+
newSpec.IncludeStrings.Should().Equal(spec.IncludeStrings);
57+
58+
newSpec.OrderExpressions.Should().NotBeSameAs(spec.OrderExpressions);
59+
newSpec.OrderExpressions.Should().Equal(spec.OrderExpressions);
60+
61+
newSpec.SearchCriterias.Should().NotBeSameAs(spec.SearchCriterias);
62+
newSpec.SearchCriterias.Should().Equal(spec.SearchCriterias);
63+
64+
newSpec.QueryTags.Should().NotBeSameAs(spec.QueryTags);
65+
newSpec.QueryTags.Should().Equal(spec.QueryTags);
66+
67+
newSpec.Take.Should().Be(spec.Take);
68+
newSpec.Skip.Should().Be(spec.Skip);
69+
newSpec.CacheKey.Should().Be(spec.CacheKey);
70+
newSpec.IgnoreQueryFilters.Should().Be(spec.IgnoreQueryFilters);
71+
newSpec.IgnoreAutoIncludes.Should().Be(spec.IgnoreAutoIncludes);
72+
newSpec.AsSplitQuery.Should().Be(spec.AsSplitQuery);
73+
newSpec.AsNoTracking.Should().Be(spec.AsNoTracking);
74+
newSpec.AsNoTrackingWithIdentityResolution.Should().Be(spec.AsNoTrackingWithIdentityResolution);
75+
newSpec.AsTracking.Should().Be(spec.AsTracking);
76+
}
77+
78+
[Fact]
79+
public void Clone_ReturnsCopyWithProjectionType()
80+
{
81+
var spec = new Specification<Customer>();
82+
spec.Items.Add("test", "test");
83+
spec.Query
84+
.Where(x => x.Name == "test")
85+
.Include(x => x.Address)
86+
.Include("Address")
87+
.OrderBy(x => x.Id)
88+
.Search(x => x.Name, "test")
89+
.Take(2)
90+
.Skip(3)
91+
.WithCacheKey("testKey")
92+
.IgnoreQueryFilters()
93+
.IgnoreQueryFilters()
94+
.AsSplitQuery()
95+
.AsNoTracking()
96+
.TagWith("testQuery1")
97+
.PostProcessingAction(x => x.Where(x => x.Id > 0));
98+
99+
var newSpec = spec.Clone<string>();
100+
101+
newSpec.Should().BeOfType<Specification<Customer, string>>();
102+
103+
newSpec.Items.Should().NotBeSameAs(spec.Items);
104+
newSpec.Items.Should().BeEquivalentTo(spec.Items);
105+
106+
newSpec.WhereExpressions.Should().NotBeSameAs(spec.WhereExpressions);
107+
newSpec.WhereExpressions.Should().Equal(spec.WhereExpressions);
108+
109+
newSpec.IncludeExpressions.Should().NotBeSameAs(spec.IncludeExpressions);
110+
newSpec.IncludeExpressions.Should().Equal(spec.IncludeExpressions);
111+
112+
newSpec.IncludeStrings.Should().NotBeSameAs(spec.IncludeStrings);
113+
newSpec.IncludeStrings.Should().Equal(spec.IncludeStrings);
114+
115+
newSpec.OrderExpressions.Should().NotBeSameAs(spec.OrderExpressions);
116+
newSpec.OrderExpressions.Should().Equal(spec.OrderExpressions);
117+
118+
newSpec.SearchCriterias.Should().NotBeSameAs(spec.SearchCriterias);
119+
newSpec.SearchCriterias.Should().Equal(spec.SearchCriterias);
120+
121+
newSpec.QueryTags.Should().NotBeSameAs(spec.QueryTags);
122+
newSpec.QueryTags.Should().Equal(spec.QueryTags);
123+
124+
newSpec.Take.Should().Be(spec.Take);
125+
newSpec.Skip.Should().Be(spec.Skip);
126+
newSpec.CacheKey.Should().Be(spec.CacheKey);
127+
newSpec.IgnoreQueryFilters.Should().Be(spec.IgnoreQueryFilters);
128+
newSpec.IgnoreAutoIncludes.Should().Be(spec.IgnoreAutoIncludes);
129+
newSpec.AsSplitQuery.Should().Be(spec.AsSplitQuery);
130+
newSpec.AsNoTracking.Should().Be(spec.AsNoTracking);
131+
newSpec.AsNoTrackingWithIdentityResolution.Should().Be(spec.AsNoTrackingWithIdentityResolution);
132+
newSpec.AsTracking.Should().Be(spec.AsTracking);
133+
}
134+
21135
#if NET8_0_OR_GREATER
22136
[Fact]
23137
public void Items_InitializesOnFirstAccess()

tests/Ardalis.Specification.Tests/Validators/SearchValidatorCustomSpecTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,5 @@ public IEnumerable<T> Evaluate(IEnumerable<T> entities)
192192
=> throw new NotImplementedException();
193193
public bool IsSatisfiedBy(T entity)
194194
=> throw new NotImplementedException();
195-
196-
void ISpecification<T>.CopyTo(Specification<T> otherSpec)
197-
{
198-
throw new NotImplementedException();
199-
}
200195
}
201196
}

0 commit comments

Comments
 (0)