Skip to content

Commit

Permalink
Update the infrastructure for single result specifications. (#272)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
fiseni authored Jun 23, 2022
1 parent 369ca41 commit c549a57
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationTo

return entity;
}

/// <inheritdoc/>
public virtual async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
Expand All @@ -51,14 +52,26 @@ public virtual async Task UpdateAsync(T entity, CancellationToken cancellationTo

await SaveChangesAsync(cancellationToken);
}


/// <inheritdoc/>
public virtual async Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
dbContext.Entry(entity).State = EntityState.Modified;
}

await SaveChangesAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
{
dbContext.Set<T>().Remove(entity);

await SaveChangesAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
Expand All @@ -78,29 +91,59 @@ public virtual async Task<T> GetByIdAsync<TId>(TId id, CancellationToken cancell
{
return await dbContext.Set<T>().FindAsync(cancellationToken: cancellationToken, new object[] { id });
}

/// <inheritdoc/>
public virtual async Task<T> GetBySpecAsync<Spec>(Spec specification, CancellationToken cancellationToken = default) where Spec : ISpecification<T>, ISingleResultSpecification
[Obsolete]
public virtual async Task<T> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
[Obsolete]
public virtual async Task<TResult> GetBySpecAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<T> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<TResult> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<T> SingleOrDefaultAsync(ISingleResultSpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<TResult> SingleOrDefaultAsync<TResult>(ISingleResultSpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<List<T>> ListAsync(CancellationToken cancellationToken = default)
{
return await dbContext.Set<T>().ToListAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<List<T>> ListAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
var queryResult = await ApplySpecification(specification).ToListAsync(cancellationToken);

return specification.PostProcessingAction == null ? queryResult : specification.PostProcessingAction(queryResult).ToList();
}

/// <inheritdoc/>
public virtual async Task<List<TResult>> ListAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -143,6 +186,7 @@ protected virtual IQueryable<T> ApplySpecification(ISpecification<T> specificati
{
return specificationEvaluator.GetQuery(dbContext.Set<T>().AsQueryable(), specification, evaluateCriteriaOnly);
}

/// <summary>
/// Filters all entities of <typeparamref name="T" />, that matches the encapsulated query logic of the
/// <paramref name="specification"/>, from the database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationTo

return entity;
}

/// <inheritdoc/>
public virtual async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
Expand All @@ -51,14 +52,23 @@ public virtual async Task UpdateAsync(T entity, CancellationToken cancellationTo

await SaveChangesAsync(cancellationToken);
}


/// <inheritdoc/>
public virtual async Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
dbContext.Set<T>().UpdateRange(entities);

await SaveChangesAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
{
dbContext.Set<T>().Remove(entity);

await SaveChangesAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default)
{
Expand All @@ -78,29 +88,59 @@ public virtual async Task<int> SaveChangesAsync(CancellationToken cancellationTo
{
return await dbContext.Set<T>().FindAsync(new object[] { id }, cancellationToken: cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<T?> GetBySpecAsync<Spec>(Spec specification, CancellationToken cancellationToken = default) where Spec : ISpecification<T>, ISingleResultSpecification
[Obsolete]
public virtual async Task<T?> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
[Obsolete]
public virtual async Task<TResult?> GetBySpecAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<T?> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<TResult?> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<T?> SingleOrDefaultAsync(ISingleResultSpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<TResult?> SingleOrDefaultAsync<TResult>(ISingleResultSpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SingleOrDefaultAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<List<T>> ListAsync(CancellationToken cancellationToken = default)
{
return await dbContext.Set<T>().ToListAsync(cancellationToken);
}

/// <inheritdoc/>
public virtual async Task<List<T>> ListAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
var queryResult = await ApplySpecification(specification).ToListAsync(cancellationToken);

return specification.PostProcessingAction == null ? queryResult : specification.PostProcessingAction(queryResult).ToList();
}

/// <inheritdoc/>
public virtual async Task<List<TResult>> ListAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -143,6 +183,7 @@ protected virtual IQueryable<T> ApplySpecification(ISpecification<T> specificati
{
return specificationEvaluator.GetQuery(dbContext.Set<T>().AsQueryable(), specification, evaluateCriteriaOnly);
}

/// <summary>
/// Filters all entities of <typeparamref name="T" />, that matches the encapsulated query logic of the
/// <paramref name="specification"/>, from the database.
Expand Down
51 changes: 49 additions & 2 deletions Specification/src/Ardalis.Specification/IReadRepositoryBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

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

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

/// <summary>
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
/// </summary>
/// <param name="specification">The encapsulated query logic.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the <typeparamref name="T" />, or <see langword="null"/>.
/// </returns>
Task<T?> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default);

/// <summary>
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
/// </summary>
/// <param name="specification">The encapsulated query logic.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the <typeparamref name="TResult" />, or <see langword="null"/>.
/// </returns>
Task<TResult?> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default);

/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The encapsulated query logic.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the <typeparamref name="T" />, or <see langword="null"/>.
/// </returns>
Task<T?> SingleOrDefaultAsync(ISingleResultSpecification<T> specification, CancellationToken cancellationToken = default);

/// <summary>
/// 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.
/// </summary>
/// <param name="specification">The encapsulated query logic.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the <typeparamref name="TResult" />, or <see langword="null"/>.
/// </returns>
Task<TResult?> SingleOrDefaultAsync<TResult>(ISingleResultSpecification<T, TResult> specification, CancellationToken cancellationToken = default);

/// <summary>
/// Finds all entities of <typeparamref name="T" /> from the database.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public interface IRepositoryBase<T> : IReadRepositoryBase<T> where T : class
/// The task result contains the <typeparamref name="T" />.
/// </returns>
Task<T> AddAsync(T entity, CancellationToken cancellationToken = default);

/// <summary>
/// Adds the given entities in the database
/// </summary>
Expand All @@ -33,24 +34,36 @@ public interface IRepositoryBase<T> : IReadRepositoryBase<T> where T : class
/// The task result contains the <typeparamref name="IEnumerable<T>" />.
/// </returns>
Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);

/// <summary>
/// Updates an entity in the database
/// </summary>
/// <param name="entity">The entity to update.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task UpdateAsync(T entity, CancellationToken cancellationToken = default);

/// <summary>
/// Updates the given entities in the database
/// </summary>
/// <param name="entities">The entities to update.</param>
/// <param name="cancellationToken"></param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task UpdateRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);

/// <summary>
/// Removes an entity in the database
/// </summary>
/// <param name="entity">The entity to delete.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task DeleteAsync(T entity, CancellationToken cancellationToken = default);

/// <summary>
/// Removes the given entities in the database
/// </summary>
/// <param name="entities">The entities to remove.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task DeleteRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);

/// <summary>
/// Persists changes to the database.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,27 @@
{
/// <summary>
/// A marker interface for specifications that are meant to return a single entity. Used to constrain methods
/// that accept a Specification and return a single result rather than a collection of results
/// that accept a Specification and return a single result rather than a collection of results.
/// </summary>
public interface ISingleResultSpecification
{
}

/// <summary>
/// Encapsulates query logic for <typeparamref name="T"/>. It is meant to return a single result.
/// </summary>
/// <typeparam name="T">The type being queried against.</typeparam>
public interface ISingleResultSpecification<T> : ISpecification<T>, ISingleResultSpecification
{
}

/// <summary>
/// Encapsulates query logic for <typeparamref name="T"/>,
/// and projects the result into <typeparamref name="TResult"/>. It is meant to return a single result.
/// </summary>
/// <typeparam name="T">The type being queried against.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
public interface ISingleResultSpecification<T, TResult> : ISpecification<T, TResult>, ISingleResultSpecification
{
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Ardalis.Specification
{
/// <inheritdoc cref="ISingleResultSpecification{T}"/>
public class SingleResultSpecification<T> : Specification<T>, ISingleResultSpecification<T>
{
}

/// <inheritdoc cref="ISingleResultSpecification{T, TResult}"/>
public class SingleResultSpecification<T, TResult> : Specification<T, TResult>, ISingleResultSpecification<T, TResult>
{
}
}
Loading

0 comments on commit c549a57

Please sign in to comment.