Skip to content

Commit 691d572

Browse files
authored
Add WithProjectionOf feature. (#460)
* Added WithProjectionOf feature. * Minor updates.
1 parent 062cc43 commit 691d572

File tree

4 files changed

+132
-0
lines changed

4 files changed

+132
-0
lines changed

src/Ardalis.Specification/ISpecification.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,6 @@ 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);
160162
}

src/Ardalis.Specification/Specification.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,52 @@ public virtual bool IsSatisfiedBy(T entity)
153153
var validator = Validator;
154154
return validator.IsValid(entity, this);
155155
}
156+
157+
void ISpecification<T>.CopyTo(Specification<T> otherSpec)
158+
{
159+
otherSpec.PostProcessingAction = PostProcessingAction;
160+
otherSpec.QueryTag = QueryTag;
161+
otherSpec.CacheKey = CacheKey;
162+
otherSpec.Take = Take;
163+
otherSpec.Skip = Skip;
164+
otherSpec.IgnoreQueryFilters = IgnoreQueryFilters;
165+
otherSpec.IgnoreAutoIncludes = IgnoreAutoIncludes;
166+
otherSpec.AsSplitQuery = AsSplitQuery;
167+
otherSpec.AsNoTracking = AsNoTracking;
168+
otherSpec.AsTracking = AsTracking;
169+
otherSpec.AsNoTrackingWithIdentityResolution = AsNoTrackingWithIdentityResolution;
170+
171+
// The expression containers are immutable, having the same instance is fine.
172+
// We'll just create new collections.
173+
174+
if (_whereExpressions is not null)
175+
{
176+
otherSpec._whereExpressions = _whereExpressions.ToList();
177+
}
178+
179+
if (_includeExpressions is not null)
180+
{
181+
otherSpec._includeExpressions = _includeExpressions.ToList();
182+
}
183+
184+
if (_includeStrings is not null)
185+
{
186+
otherSpec._includeStrings = _includeStrings.ToList();
187+
}
188+
189+
if (_orderExpressions is not null)
190+
{
191+
otherSpec._orderExpressions = _orderExpressions.ToList();
192+
}
193+
194+
if (_searchExpressions is not null)
195+
{
196+
otherSpec._searchExpressions = _searchExpressions.ToList();
197+
}
198+
199+
if (_items is not null)
200+
{
201+
otherSpec._items = new Dictionary<string, object>(_items);
202+
}
203+
}
156204
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Ardalis.Specification;
2+
3+
public static class SpecificationExtensions
4+
{
5+
public static Specification<T, TResult> WithProjectionOf<T, TResult>(this ISpecification<T> source, ISpecification<T, TResult> projectionSpec)
6+
{
7+
var newSpec = new Specification<T, TResult>();
8+
source.CopyTo(newSpec);
9+
newSpec.Selector = projectionSpec.Selector;
10+
newSpec.SelectorMany = projectionSpec.SelectorMany;
11+
newSpec.PostProcessingAction = projectionSpec.PostProcessingAction;
12+
return newSpec;
13+
}
14+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
namespace Tests;
2+
3+
public class SpecificationExtensionsTests
4+
{
5+
private record Address(int Id, string Street);
6+
private record Person(int Id, string Name, List<string> Names, Address Address);
7+
8+
[Fact]
9+
public void WithProjectionOf_ReturnsCopyWithProjection()
10+
{
11+
var spec = new Specification<Person>();
12+
spec.Items.Add("test", "test");
13+
spec.Query
14+
.Where(x => x.Name == "test")
15+
.Include(x => x.Address)
16+
.Include("Address")
17+
.OrderBy(x => x.Id)
18+
.Search(x => x.Name, "test")
19+
.Take(2)
20+
.Skip(3)
21+
.WithCacheKey("testKey")
22+
.IgnoreQueryFilters()
23+
.IgnoreQueryFilters()
24+
.AsSplitQuery()
25+
.AsNoTracking()
26+
.TagWith("testQuery")
27+
.PostProcessingAction(x => x.Where(x => x.Id > 0));
28+
29+
var projectionSpec = new Specification<Person, string>();
30+
projectionSpec.Query.Select(x => x.Name);
31+
projectionSpec.Query.SelectMany(x => x.Names);
32+
projectionSpec.Query.PostProcessingAction(x => x.Select(x => x + "A"));
33+
34+
var newSpec = spec.WithProjectionOf(projectionSpec);
35+
36+
newSpec.Items.Should().NotBeSameAs(spec.Items);
37+
newSpec.Items.Should().BeEquivalentTo(spec.Items);
38+
39+
newSpec.WhereExpressions.Should().NotBeSameAs(spec.WhereExpressions);
40+
newSpec.WhereExpressions.Should().Equal(spec.WhereExpressions);
41+
42+
newSpec.IncludeExpressions.Should().NotBeSameAs(spec.IncludeExpressions);
43+
newSpec.IncludeExpressions.Should().Equal(spec.IncludeExpressions);
44+
45+
newSpec.IncludeStrings.Should().NotBeSameAs(spec.IncludeStrings);
46+
newSpec.IncludeStrings.Should().Equal(spec.IncludeStrings);
47+
48+
newSpec.OrderExpressions.Should().NotBeSameAs(spec.OrderExpressions);
49+
newSpec.OrderExpressions.Should().Equal(spec.OrderExpressions);
50+
51+
newSpec.SearchCriterias.Should().NotBeSameAs(spec.SearchCriterias);
52+
newSpec.SearchCriterias.Should().Equal(spec.SearchCriterias);
53+
54+
newSpec.Take.Should().Be(spec.Take);
55+
newSpec.Skip.Should().Be(spec.Skip);
56+
newSpec.CacheKey.Should().Be(spec.CacheKey);
57+
newSpec.IgnoreQueryFilters.Should().Be(spec.IgnoreQueryFilters);
58+
newSpec.IgnoreAutoIncludes.Should().Be(spec.IgnoreAutoIncludes);
59+
newSpec.AsSplitQuery.Should().Be(spec.AsSplitQuery);
60+
newSpec.AsNoTracking.Should().Be(spec.AsNoTracking);
61+
newSpec.AsNoTrackingWithIdentityResolution.Should().Be(spec.AsNoTrackingWithIdentityResolution);
62+
newSpec.AsTracking.Should().Be(spec.AsTracking);
63+
newSpec.QueryTag.Should().Be(spec.QueryTag);
64+
65+
newSpec.PostProcessingAction.Should().BeSameAs(projectionSpec.PostProcessingAction);
66+
((Specification<Person>)newSpec).PostProcessingAction.Should().BeSameAs(spec.PostProcessingAction);
67+
}
68+
}

0 commit comments

Comments
 (0)