Skip to content

Commit

Permalink
Ardalis/cleanup (#332)
Browse files Browse the repository at this point in the history
* working on docs and cleaning up sample

* add docs for evaluator
  • Loading branch information
ardalis authored May 26, 2023
1 parent 0805a1e commit 62ca358
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public interface IReadRepositoryBase<T> where T : class
/// </summary>
/// <typeparam name="TId">The type of primary key.</typeparam>
/// <param name="id">The value of the primary key for the entity to be found.</param>
/// <param name="cancellationToken"></param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the <typeparamref name="T" />, or <see langword="null"/>.
Expand Down
1 change: 1 addition & 0 deletions Specification/src/Ardalis.Specification/IRepositoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface IRepositoryBase<T> : IReadRepositoryBase<T> where T : class
/// Adds an entity in the database.
/// </summary>
/// <param name="entity">The entity to add.</param>
/// <param name="cancellationToken"></param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result contains the <typeparamref name="T" />.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
namespace Ardalis.Specification
using System;

namespace Ardalis.Specification
{
/// <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.
/// </summary>
[Obsolete("Use ISingleResultSpecification<T> instead. This interface will be removed in a future version of Ardalis.Specification.")]
public interface ISingleResultSpecification
{
}
Expand All @@ -12,7 +15,7 @@ public interface ISingleResultSpecification
/// 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
public interface ISingleResultSpecification<T> : ISpecification<T>//, ISingleResultSpecification
{
}

Expand All @@ -22,7 +25,7 @@ public interface ISingleResultSpecification<T> : ISpecification<T>, ISingleResul
/// </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
public interface ISingleResultSpecification<T, TResult> : ISpecification<T, TResult>//, ISingleResultSpecification
{
}
}
57 changes: 56 additions & 1 deletion docs/extensions/extend-define-evaluators.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,59 @@ parent: Extensions
nav_order: 3
---

How to extend or define your own evaluators
How to extend or define your own evaluators.

## Evaluators

Evaluators are used within the specification to compose the query that will be executed. You can add your own evaluators to extend the behavior of the base Specification class.

Here is an example:

```csharp
public class MyPartialEvaluator : IEvaluator
{
private MyPartialEvaluator () { }
public static MyPartialEvaluator Instance { get; } = new MyPartialEvaluator();

public bool IsCriteriaEvaluator { get; } = true;

public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
{
// Write your desired implementation
return query;
}
}

public class MySpecificationEvaluator : SpecificationEvaluator
{
public static MySpecificationEvaluator Instance { get; } = new MySpecificationEvaluator();

private MySpecificationEvaluator() : base()
{
Evaluators.Add(MyPartialEvaluator.Instance);
}
}
```

To use the evaluator, you would pass it into your repository implementation's constructor:

```csharp
public class Repository<T> : RepositoryBase<T>, IRepository<T> where T : class
{
public Repository(AppDbContext dbContext)
: base(dbContext, MySpecificationEvaluator.Instance)
{
}
}
```

Of course you would also need to register the service in `Program.cs`:

```csharp
builder.Services.AddScoped<ISpecificationEvaluator, MySpecificationEvaluator>();
```

## References

- [Enabled by PR 328](https://github.com/ardalis/Specification/pull/328)
3 changes: 1 addition & 2 deletions docs/extensions/extend-specification-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ parent: Extensions
nav_order: 2
---


# How to add extensions to the specification builder

The specification builder from `Ardalis.Specification` is extensible by design. In fact, the methods you can use out of the box are implemented as extension methods themselves (check out the [source code](https://github.com/ardalis/Specification/blob/main/Specification/src/Ardalis.Specification/Builder/SpecificationBuilderExtensions.cs)). Your project might have requirements that cannot be satisfied by the existing toolset of course, or you might want to simplify repetitive code in several specification constructors. Whatever your case, enhancing the default builder is easy by creating your own extension methods.
Expand All @@ -20,7 +19,7 @@ Query.AsNoTracking()
From here you can inspect the return type of the builder method you chained it to (`AsNoTracking`), and create an extension method on that interface (it doesn't need to be chained of course -- working on `Query` itself is also valid). This will most likely be `ISpecificationBuilder<T>`, but in some cases it's an inherited inteface. The example below illustrates how extension methods on inherited interfaces allow the builder to offer specific methods in specific contexts.


## Example: Configure caching behaviour through specification builder extension method
## Example: Configure caching behavior through specification builder extension method

In order to achieve this (note the `.WithTimeToLive` method):

Expand Down
4 changes: 2 additions & 2 deletions docs/usage/create-specifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ public class ItemByIdSpec : Specification<Item>
}
```

Finally: the Specification above should also implement the marker interface `ISingleResultSpecification`, which makes clear that this Specification will return only one result. Any "ById" Specification, and any other Specification intended to return only one result, should implement this interface to make clear that it returns a single result.
Finally: the Specification above should also implement the marker interface `ISingleResultSpecification<T>`, which makes clear that this Specification will return only one result. Any "ById" Specification, and any other Specification intended to return only one result, should implement this interface to make clear that it returns a single result.

```csharp
public class ItemByIdSpec : Specification<Item>, ISingleResultSpecification
public class ItemByIdSpec : SingleResultSpecification<Item>
{
public ItemByIdSpec(int Id)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

namespace Ardalis.SampleApp.Core.Specifications
{
public class CustomerByNameSpec : Specification<Customer>, ISingleResultSpecification
/// <summary>
/// This specification expects customer names to be unique - change the base type if you want to support multiple results
/// </summary>
public class CustomerByNameSpec : SingleResultSpecification<Customer>
{
public CustomerByNameSpec(string name)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

namespace Ardalis.SampleApp.Core.Specifications
{
public class CustomerByNameWithStoresSpec : Specification<Customer>, ISingleResultSpecification
/// <summary>
/// This specification expects customer names to be unique - change the base type if you want to support multiple results
/// </summary>
public class CustomerByNameWithStoresSpec : SingleResultSpecification<Customer>
{
public CustomerByNameWithStoresSpec(string name)
{
Expand Down
3 changes: 3 additions & 0 deletions sample/Ardalis.SampleApp.Core/Specifications/CustomerSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Ardalis.SampleApp.Core.Specifications
{
/// <summary>
/// This specification expects 0 to many results
/// </summary>
public class CustomerSpec : Specification<Customer>
{
public CustomerSpec(CustomerFilter filter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public Task<T> GetByIdAsync<TId>(TId id, CancellationToken cancellationToken = d
}

/// <inheritdoc/>
public Task<T> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
public Task<T?> FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
if (specification.CacheEnabled)
{
Expand All @@ -86,24 +86,18 @@ public Task<T> GetBySpecAsync(ISpecification<T> specification, CancellationToken
{
entry.SetOptions(_cacheOptions);
_logger.LogWarning("Fetching source data for " + key);
return _sourceRepository.GetBySpecAsync(specification, cancellationToken);
return _sourceRepository.FirstOrDefaultAsync(specification, cancellationToken);
});
}
return _sourceRepository.GetBySpecAsync(specification, cancellationToken);
return _sourceRepository.FirstOrDefaultAsync(specification, cancellationToken);
}

/// <inheritdoc/>
public Task<TResult> GetBySpecAsync<TResult>(Specification.ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
public Task<TResult> GetBySpecAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}

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

/// <inheritdoc/>
public virtual async Task<TResult?> FirstOrDefaultAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -134,7 +128,7 @@ public Task<List<T>> ListAsync(CancellationToken cancellationToken = default)
}

/// <inheritdoc/>
public Task<List<T>> ListAsync(Specification.ISpecification<T> specification, CancellationToken cancellationToken = default)
public Task<List<T>> ListAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
if (specification.CacheEnabled)
{
Expand All @@ -151,7 +145,13 @@ public Task<List<T>> ListAsync(Specification.ISpecification<T> specification, Ca
}

/// <inheritdoc/>
public Task<List<TResult>> ListAsync<TResult>(Specification.ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
public Task<List<TResult>> ListAsync<TResult>(ISpecification<T, TResult> specification, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}

/// <inheritdoc/>
public Task<T> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
Expand Down

0 comments on commit 62ca358

Please sign in to comment.