From 62ca3584c74c9284ece4a0d4be4f644dd35bd986 Mon Sep 17 00:00:00 2001 From: Steve Smith Date: Fri, 26 May 2023 12:28:19 -0400 Subject: [PATCH] Ardalis/cleanup (#332) * working on docs and cleaning up sample * add docs for evaluator --- .../IReadRepositoryBase.cs | 1 + .../Ardalis.Specification/IRepositoryBase.cs | 1 + .../ISingleResultSpecification.cs | 9 ++- docs/extensions/extend-define-evaluators.md | 57 ++++++++++++++++++- .../extend-specification-builder.md | 3 +- docs/usage/create-specifications.md | 4 +- .../Specifications/CustomerByNameSpec.cs | 5 +- .../CustomerByNameWithStoresSpec.cs | 5 +- .../Specifications/CustomerSpec.cs | 3 + .../Data/CachedCustomerRepository.cs | 24 ++++---- 10 files changed, 90 insertions(+), 22 deletions(-) diff --git a/Specification/src/Ardalis.Specification/IReadRepositoryBase.cs b/Specification/src/Ardalis.Specification/IReadRepositoryBase.cs index 12de01a1..5867f84d 100644 --- a/Specification/src/Ardalis.Specification/IReadRepositoryBase.cs +++ b/Specification/src/Ardalis.Specification/IReadRepositoryBase.cs @@ -19,6 +19,7 @@ public interface IReadRepositoryBase where T : class /// /// The type of primary key. /// The value of the primary key for the entity to be found. + /// /// /// A task that represents the asynchronous operation. /// The task result contains the , or . diff --git a/Specification/src/Ardalis.Specification/IRepositoryBase.cs b/Specification/src/Ardalis.Specification/IRepositoryBase.cs index 38e560ee..4ab72502 100644 --- a/Specification/src/Ardalis.Specification/IRepositoryBase.cs +++ b/Specification/src/Ardalis.Specification/IRepositoryBase.cs @@ -18,6 +18,7 @@ public interface IRepositoryBase : IReadRepositoryBase where T : class /// Adds an entity in the database. /// /// The entity to add. + /// /// /// A task that represents the asynchronous operation. /// The task result contains the . diff --git a/Specification/src/Ardalis.Specification/ISingleResultSpecification.cs b/Specification/src/Ardalis.Specification/ISingleResultSpecification.cs index 715bdcd3..407d69eb 100644 --- a/Specification/src/Ardalis.Specification/ISingleResultSpecification.cs +++ b/Specification/src/Ardalis.Specification/ISingleResultSpecification.cs @@ -1,9 +1,12 @@ -namespace Ardalis.Specification +using System; + +namespace Ardalis.Specification { /// /// 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. /// + [Obsolete("Use ISingleResultSpecification instead. This interface will be removed in a future version of Ardalis.Specification.")] public interface ISingleResultSpecification { } @@ -12,7 +15,7 @@ public interface ISingleResultSpecification /// Encapsulates query logic for . It is meant to return a single result. /// /// The type being queried against. - public interface ISingleResultSpecification : ISpecification, ISingleResultSpecification + public interface ISingleResultSpecification : ISpecification//, ISingleResultSpecification { } @@ -22,7 +25,7 @@ public interface ISingleResultSpecification : ISpecification, ISingleResul /// /// The type being queried against. /// The type of the result. - public interface ISingleResultSpecification : ISpecification, ISingleResultSpecification + public interface ISingleResultSpecification : ISpecification//, ISingleResultSpecification { } } diff --git a/docs/extensions/extend-define-evaluators.md b/docs/extensions/extend-define-evaluators.md index b4710fbd..dc234f0e 100644 --- a/docs/extensions/extend-define-evaluators.md +++ b/docs/extensions/extend-define-evaluators.md @@ -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 GetQuery(IQueryable query, ISpecification 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 : RepositoryBase, IRepository 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(); +``` + +## References + +- [Enabled by PR 328](https://github.com/ardalis/Specification/pull/328) \ No newline at end of file diff --git a/docs/extensions/extend-specification-builder.md b/docs/extensions/extend-specification-builder.md index 6b547ad7..52773c33 100644 --- a/docs/extensions/extend-specification-builder.md +++ b/docs/extensions/extend-specification-builder.md @@ -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. @@ -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`, 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): diff --git a/docs/usage/create-specifications.md b/docs/usage/create-specifications.md index f7ca6817..52c8fa8b 100644 --- a/docs/usage/create-specifications.md +++ b/docs/usage/create-specifications.md @@ -39,10 +39,10 @@ public class ItemByIdSpec : Specification } ``` -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`, 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, ISingleResultSpecification +public class ItemByIdSpec : SingleResultSpecification { public ItemByIdSpec(int Id) { diff --git a/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameSpec.cs b/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameSpec.cs index 8335f5a2..5f255d0f 100644 --- a/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameSpec.cs +++ b/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameSpec.cs @@ -3,7 +3,10 @@ namespace Ardalis.SampleApp.Core.Specifications { - public class CustomerByNameSpec : Specification, ISingleResultSpecification + /// + /// This specification expects customer names to be unique - change the base type if you want to support multiple results + /// + public class CustomerByNameSpec : SingleResultSpecification { public CustomerByNameSpec(string name) { diff --git a/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameWithStoresSpec.cs b/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameWithStoresSpec.cs index 6f9f9335..844eb23c 100644 --- a/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameWithStoresSpec.cs +++ b/sample/Ardalis.SampleApp.Core/Specifications/CustomerByNameWithStoresSpec.cs @@ -3,7 +3,10 @@ namespace Ardalis.SampleApp.Core.Specifications { - public class CustomerByNameWithStoresSpec : Specification, ISingleResultSpecification + /// + /// This specification expects customer names to be unique - change the base type if you want to support multiple results + /// + public class CustomerByNameWithStoresSpec : SingleResultSpecification { public CustomerByNameWithStoresSpec(string name) { diff --git a/sample/Ardalis.SampleApp.Core/Specifications/CustomerSpec.cs b/sample/Ardalis.SampleApp.Core/Specifications/CustomerSpec.cs index 7cfe5522..d6184c55 100644 --- a/sample/Ardalis.SampleApp.Core/Specifications/CustomerSpec.cs +++ b/sample/Ardalis.SampleApp.Core/Specifications/CustomerSpec.cs @@ -4,6 +4,9 @@ namespace Ardalis.SampleApp.Core.Specifications { + /// + /// This specification expects 0 to many results + /// public class CustomerSpec : Specification { public CustomerSpec(CustomerFilter filter) diff --git a/sample/Ardalis.SampleApp.Infrastructure/Data/CachedCustomerRepository.cs b/sample/Ardalis.SampleApp.Infrastructure/Data/CachedCustomerRepository.cs index 3835d5a5..7775098d 100644 --- a/sample/Ardalis.SampleApp.Infrastructure/Data/CachedCustomerRepository.cs +++ b/sample/Ardalis.SampleApp.Infrastructure/Data/CachedCustomerRepository.cs @@ -76,7 +76,7 @@ public Task GetByIdAsync(TId id, CancellationToken cancellationToken = d } /// - public Task GetBySpecAsync(ISpecification specification, CancellationToken cancellationToken = default) + public Task FirstOrDefaultAsync(ISpecification specification, CancellationToken cancellationToken = default) { if (specification.CacheEnabled) { @@ -86,24 +86,18 @@ public Task GetBySpecAsync(ISpecification 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); } /// - public Task GetBySpecAsync(Specification.ISpecification specification, CancellationToken cancellationToken = default) + public Task GetBySpecAsync(ISpecification specification, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - /// - public virtual async Task FirstOrDefaultAsync(ISpecification specification, CancellationToken cancellationToken = default) - { - return await _sourceRepository.FirstOrDefaultAsync(specification, cancellationToken); - } - /// public virtual async Task FirstOrDefaultAsync(ISpecification specification, CancellationToken cancellationToken = default) { @@ -134,7 +128,7 @@ public Task> ListAsync(CancellationToken cancellationToken = default) } /// - public Task> ListAsync(Specification.ISpecification specification, CancellationToken cancellationToken = default) + public Task> ListAsync(ISpecification specification, CancellationToken cancellationToken = default) { if (specification.CacheEnabled) { @@ -151,7 +145,13 @@ public Task> ListAsync(Specification.ISpecification specification, Ca } /// - public Task> ListAsync(Specification.ISpecification specification, CancellationToken cancellationToken = default) + public Task> ListAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + /// + public Task GetBySpecAsync(ISpecification specification, CancellationToken cancellationToken = default) { throw new NotImplementedException(); }