Skip to content

Commit c549a57

Browse files
authored
Update the infrastructure for single result specifications. (#272)
* Added new contracts and base classes for single result specifications. * Updated repository with verbose methods. Marked GetBySpec methods as obsolete. * Removed the constraint for GetBySpec method. * Added UpdateRangeAsync repository method. * Update XML comment.
1 parent 369ca41 commit c549a57

File tree

8 files changed

+207
-18
lines changed

8 files changed

+207
-18
lines changed

Specification.EntityFramework6/src/Ardalis.Specification.EntityFramework6/RepositoryBaseOfT.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationTo
3434

3535
return entity;
3636
}
37+
3738
/// <inheritdoc/>
3839
public virtual async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
3940
{
@@ -51,14 +52,26 @@ public virtual async Task UpdateAsync(T entity, CancellationToken cancellationTo
5152

5253
await SaveChangesAsync(cancellationToken);
5354
}
54-
55+
56+
/// <inheritdoc/>
57+
public virtual async Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
58+
{
59+
foreach (var entity in entities)
60+
{
61+
dbContext.Entry(entity).State = EntityState.Modified;
62+
}
63+
64+
await SaveChangesAsync(cancellationToken);
65+
}
66+
5567
/// <inheritdoc/>
5668
public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
5769
{
5870
dbContext.Set<T>().Remove(entity);
5971

6072
await SaveChangesAsync(cancellationToken);
6173
}
74+
6275
/// <inheritdoc/>
6376
public virtual async Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
6477
{
@@ -78,29 +91,59 @@ public virtual async Task<T> GetByIdAsync<TId>(TId id, CancellationToken cancell
7891
{
7992
return await dbContext.Set<T>().FindAsync(cancellationToken: cancellationToken, new object[] { id });
8093
}
94+
8195
/// <inheritdoc/>
82-
public virtual async Task<T> GetBySpecAsync<Spec>(Spec specification, CancellationToken cancellationToken = default) where Spec : ISpecification<T>, ISingleResultSpecification
96+
[Obsolete]
97+
public virtual async Task<T> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
8398
{
8499
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
85100
}
101+
86102
/// <inheritdoc/>
103+
[Obsolete]
87104
public virtual async Task<TResult> GetBySpecAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
88105
{
89106
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
90107
}
91108

109+
/// <inheritdoc/>
110+
public virtual async Task<T> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
111+
{
112+
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
113+
}
114+
115+
/// <inheritdoc/>
116+
public virtual async Task<TResult> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
117+
{
118+
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
119+
}
120+
121+
/// <inheritdoc/>
122+
public virtual async Task<T> SingleOrDefaultAsync(ISingleResultSpecification<T> specification, CancellationToken cancellationToken = default)
123+
{
124+
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
125+
}
126+
127+
/// <inheritdoc/>
128+
public virtual async Task<TResult> SingleOrDefaultAsync<TResult>(ISingleResultSpecification<T, TResult> specification, CancellationToken cancellationToken = default)
129+
{
130+
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
131+
}
132+
92133
/// <inheritdoc/>
93134
public virtual async Task<List<T>> ListAsync(CancellationToken cancellationToken = default)
94135
{
95136
return await dbContext.Set<T>().ToListAsync(cancellationToken);
96137
}
138+
97139
/// <inheritdoc/>
98140
public virtual async Task<List<T>> ListAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
99141
{
100142
var queryResult = await ApplySpecification(specification).ToListAsync(cancellationToken);
101143

102144
return specification.PostProcessingAction == null ? queryResult : specification.PostProcessingAction(queryResult).ToList();
103145
}
146+
104147
/// <inheritdoc/>
105148
public virtual async Task<List<TResult>> ListAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
106149
{
@@ -143,6 +186,7 @@ protected virtual IQueryable<T> ApplySpecification(ISpecification<T> specificati
143186
{
144187
return specificationEvaluator.GetQuery(dbContext.Set<T>().AsQueryable(), specification, evaluateCriteriaOnly);
145188
}
189+
146190
/// <summary>
147191
/// Filters all entities of <typeparamref name="T" />, that matches the encapsulated query logic of the
148192
/// <paramref name="specification"/>, from the database.

Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/RepositoryBaseOfT.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationTo
3434

3535
return entity;
3636
}
37+
3738
/// <inheritdoc/>
3839
public virtual async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
3940
{
@@ -51,14 +52,23 @@ public virtual async Task UpdateAsync(T entity, CancellationToken cancellationTo
5152

5253
await SaveChangesAsync(cancellationToken);
5354
}
54-
55+
56+
/// <inheritdoc/>
57+
public virtual async Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
58+
{
59+
dbContext.Set<T>().UpdateRange(entities);
60+
61+
await SaveChangesAsync(cancellationToken);
62+
}
63+
5564
/// <inheritdoc/>
5665
public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
5766
{
5867
dbContext.Set<T>().Remove(entity);
5968

6069
await SaveChangesAsync(cancellationToken);
6170
}
71+
6272
/// <inheritdoc/>
6373
public virtual async Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
6474
{
@@ -78,29 +88,59 @@ public virtual async Task<int> SaveChangesAsync(CancellationToken cancellationTo
7888
{
7989
return await dbContext.Set<T>().FindAsync(new object[] { id }, cancellationToken: cancellationToken);
8090
}
91+
8192
/// <inheritdoc/>
82-
public virtual async Task<T?> GetBySpecAsync<Spec>(Spec specification, CancellationToken cancellationToken = default) where Spec : ISpecification<T>, ISingleResultSpecification
93+
[Obsolete]
94+
public virtual async Task<T?> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
8395
{
8496
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
8597
}
98+
8699
/// <inheritdoc/>
100+
[Obsolete]
87101
public virtual async Task<TResult?> GetBySpecAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
88102
{
89103
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
90104
}
91105

106+
/// <inheritdoc/>
107+
public virtual async Task<T?> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
108+
{
109+
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
110+
}
111+
112+
/// <inheritdoc/>
113+
public virtual async Task<TResult?> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
114+
{
115+
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
116+
}
117+
118+
/// <inheritdoc/>
119+
public virtual async Task<T?> SingleOrDefaultAsync(ISingleResultSpecification<T> specification, CancellationToken cancellationToken = default)
120+
{
121+
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
122+
}
123+
124+
/// <inheritdoc/>
125+
public virtual async Task<TResult?> SingleOrDefaultAsync<TResult>(ISingleResultSpecification<T, TResult> specification, CancellationToken cancellationToken = default)
126+
{
127+
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
128+
}
129+
92130
/// <inheritdoc/>
93131
public virtual async Task<List<T>> ListAsync(CancellationToken cancellationToken = default)
94132
{
95133
return await dbContext.Set<T>().ToListAsync(cancellationToken);
96134
}
135+
97136
/// <inheritdoc/>
98137
public virtual async Task<List<T>> ListAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
99138
{
100139
var queryResult = await ApplySpecification(specification).ToListAsync(cancellationToken);
101140

102141
return specification.PostProcessingAction == null ? queryResult : specification.PostProcessingAction(queryResult).ToList();
103142
}
143+
104144
/// <inheritdoc/>
105145
public virtual async Task<List<TResult>> ListAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
106146
{
@@ -143,6 +183,7 @@ protected virtual IQueryable<T> ApplySpecification(ISpecification<T> specificati
143183
{
144184
return specificationEvaluator.GetQuery(dbContext.Set<T>().AsQueryable(), specification, evaluateCriteriaOnly);
145185
}
186+
146187
/// <summary>
147188
/// Filters all entities of <typeparamref name="T" />, that matches the encapsulated query logic of the
148189
/// <paramref name="specification"/>, from the database.

Specification/src/Ardalis.Specification/IReadRepositoryBase.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Threading;
34
using System.Threading.Tasks;
45

@@ -32,7 +33,8 @@ public interface IReadRepositoryBase<T> where T : class
3233
/// A task that represents the asynchronous operation.
3334
/// The task result contains the <typeparamref name="T" />, or <see langword="null"/>.
3435
/// </returns>
35-
Task<T?> GetBySpecAsync<Spec>(Spec specification, CancellationToken cancellationToken = default) where Spec : ISingleResultSpecification, ISpecification<T>;
36+
[Obsolete]
37+
Task<T?> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default);
3638

3739
/// <summary>
3840
/// Finds an entity that matches the encapsulated query logic of the <paramref name="specification"/>.
@@ -43,8 +45,53 @@ public interface IReadRepositoryBase<T> where T : class
4345
/// A task that represents the asynchronous operation.
4446
/// The task result contains the <typeparamref name="TResult" />.
4547
/// </returns>
48+
[Obsolete]
4649
Task<TResult?> GetBySpecAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default);
4750

51+
/// <summary>
52+
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
53+
/// </summary>
54+
/// <param name="specification">The encapsulated query logic.</param>
55+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
56+
/// <returns>
57+
/// A task that represents the asynchronous operation.
58+
/// The task result contains the <typeparamref name="T" />, or <see langword="null"/>.
59+
/// </returns>
60+
Task<T?> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default);
61+
62+
/// <summary>
63+
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
64+
/// </summary>
65+
/// <param name="specification">The encapsulated query logic.</param>
66+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
67+
/// <returns>
68+
/// A task that represents the asynchronous operation.
69+
/// The task result contains the <typeparamref name="TResult" />, or <see langword="null"/>.
70+
/// </returns>
71+
Task<TResult?> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default);
72+
73+
/// <summary>
74+
/// Returns the only element of a sequence, or a default value if the sequence is empty; this method throws an exception if there is more than one element in the sequence.
75+
/// </summary>
76+
/// <param name="specification">The encapsulated query logic.</param>
77+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
78+
/// <returns>
79+
/// A task that represents the asynchronous operation.
80+
/// The task result contains the <typeparamref name="T" />, or <see langword="null"/>.
81+
/// </returns>
82+
Task<T?> SingleOrDefaultAsync(ISingleResultSpecification<T> specification, CancellationToken cancellationToken = default);
83+
84+
/// <summary>
85+
/// Returns the only element of a sequence, or a default value if the sequence is empty; this method throws an exception if there is more than one element in the sequence.
86+
/// </summary>
87+
/// <param name="specification">The encapsulated query logic.</param>
88+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
89+
/// <returns>
90+
/// A task that represents the asynchronous operation.
91+
/// The task result contains the <typeparamref name="TResult" />, or <see langword="null"/>.
92+
/// </returns>
93+
Task<TResult?> SingleOrDefaultAsync<TResult>(ISingleResultSpecification<T, TResult> specification, CancellationToken cancellationToken = default);
94+
4895
/// <summary>
4996
/// Finds all entities of <typeparamref name="T" /> from the database.
5097
/// </summary>

Specification/src/Ardalis.Specification/IRepositoryBaseOfT.cs renamed to Specification/src/Ardalis.Specification/IRepositoryBase.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public interface IRepositoryBase<T> : IReadRepositoryBase<T> where T : class
2323
/// The task result contains the <typeparamref name="T" />.
2424
/// </returns>
2525
Task<T> AddAsync(T entity, CancellationToken cancellationToken = default);
26+
2627
/// <summary>
2728
/// Adds the given entities in the database
2829
/// </summary>
@@ -33,24 +34,36 @@ public interface IRepositoryBase<T> : IReadRepositoryBase<T> where T : class
3334
/// The task result contains the <typeparamref name="IEnumerable<T>" />.
3435
/// </returns>
3536
Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
37+
3638
/// <summary>
3739
/// Updates an entity in the database
3840
/// </summary>
3941
/// <param name="entity">The entity to update.</param>
4042
/// <returns>A task that represents the asynchronous operation.</returns>
4143
Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
44+
45+
/// <summary>
46+
/// Updates the given entities in the database
47+
/// </summary>
48+
/// <param name="entities">The entities to update.</param>
49+
/// <param name="cancellationToken"></param>
50+
/// <returns>A task that represents the asynchronous operation.</returns>
51+
Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
52+
4253
/// <summary>
4354
/// Removes an entity in the database
4455
/// </summary>
4556
/// <param name="entity">The entity to delete.</param>
4657
/// <returns>A task that represents the asynchronous operation.</returns>
4758
Task DeleteAsync(T entity, CancellationToken cancellationToken = default);
59+
4860
/// <summary>
4961
/// Removes the given entities in the database
5062
/// </summary>
5163
/// <param name="entities">The entities to remove.</param>
5264
/// <returns>A task that represents the asynchronous operation.</returns>
5365
Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
66+
5467
/// <summary>
5568
/// Persists changes to the database.
5669
/// </summary>

Specification/src/Ardalis.Specification/ISingleResultSpecification.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,27 @@
22
{
33
/// <summary>
44
/// A marker interface for specifications that are meant to return a single entity. Used to constrain methods
5-
/// that accept a Specification and return a single result rather than a collection of results
5+
/// that accept a Specification and return a single result rather than a collection of results.
66
/// </summary>
77
public interface ISingleResultSpecification
88
{
99
}
10+
11+
/// <summary>
12+
/// Encapsulates query logic for <typeparamref name="T"/>. It is meant to return a single result.
13+
/// </summary>
14+
/// <typeparam name="T">The type being queried against.</typeparam>
15+
public interface ISingleResultSpecification<T> : ISpecification<T>, ISingleResultSpecification
16+
{
17+
}
18+
19+
/// <summary>
20+
/// Encapsulates query logic for <typeparamref name="T"/>,
21+
/// and projects the result into <typeparamref name="TResult"/>. It is meant to return a single result.
22+
/// </summary>
23+
/// <typeparam name="T">The type being queried against.</typeparam>
24+
/// <typeparam name="TResult">The type of the result.</typeparam>
25+
public interface ISingleResultSpecification<T, TResult> : ISpecification<T, TResult>, ISingleResultSpecification
26+
{
27+
}
1028
}

Specification/src/Ardalis.Specification/ISingleResultSpecificationOfT.cs

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Ardalis.Specification
2+
{
3+
/// <inheritdoc cref="ISingleResultSpecification{T}"/>
4+
public class SingleResultSpecification<T> : Specification<T>, ISingleResultSpecification<T>
5+
{
6+
}
7+
8+
/// <inheritdoc cref="ISingleResultSpecification{T, TResult}"/>
9+
public class SingleResultSpecification<T, TResult> : Specification<T, TResult>, ISingleResultSpecification<T, TResult>
10+
{
11+
}
12+
}

0 commit comments

Comments
 (0)