https://github.com/murilobeltrame/hello-dotnet-specification-pattern
My Take on Specification Pattern with .Net Core and EF Core
https://github.com/murilobeltrame/hello-dotnet-specification-pattern
csharp dotnet-core entity-framework-core patterns
Last synced: 2 months ago
JSON representation
My Take on Specification Pattern with .Net Core and EF Core
- Host: GitHub
- URL: https://github.com/murilobeltrame/hello-dotnet-specification-pattern
- Owner: murilobeltrame
- License: mit
- Created: 2022-11-08T21:02:57.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-11-28T13:35:15.000Z (over 3 years ago)
- Last Synced: 2025-06-20T11:45:48.827Z (about 1 year ago)
- Topics: csharp, dotnet-core, entity-framework-core, patterns
- Language: C#
- Homepage:
- Size: 40 KB
- Stars: 3
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.MD
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Specification Pattern tryout
This repo covers my take on how to implement Specification Pattern using .Net Core and Entity Framework Core.
Specification Pattern is specially useful when expressing business logic throught repository manipulation. More on [Specification Pattern](https://en.wikipedia.org/wiki/Specification_pattern).
I preffer to implement Specification Pattern combined with Repository Pattern, so there`s the highlights:
[Specification generic base class](src/SpecificationPattern.Application/Specifications/_BaseSpecification_T_.cs):
```cs
public abstract class BaseSpecification : ISpecification where TEntity : BaseEntity
{
public IList>> WhereExpressions { get; protected set; } = new List>>();
public IList>> IncludeExpressions { get; protected set; } = new List>>();
public IList>> OrderByExpressions { get; protected set; } = new List>>();
public ushort? Take { get; protected set; }
public uint? Skip { get; protected set; }
public Expression>? SelectExpression { get; protected set; }
}
```
[Repository genric base class](src/SpecificationPattern.Infra/Repositories/Repository_T_.cs):
```cs
public class Repository : IRepository where TEntity : BaseEntity
{
private readonly ApplicationContext _db;
public Repository(ApplicationContext db)
{
_db = db;
}
public async Task CreateAsync(TEntity record, CancellationToken cancellationToken = default) =>
(await _db.Set().AddAsync(record, cancellationToken)).Entity;
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var entitity = await GetAsync(new GetByIdSpecification(id), cancellationToken);
_db.Set().Remove(entitity);
}
public async Task ExistsAsync(ISpecification specification, CancellationToken cancellationToken = default) =>
(await GetAsync(specification, cancellationToken)) != null;
public async Task GetAsync(ISpecification specification, CancellationToken cancellationToken = default)
{
var query = ProcessQuery(specification);
var result = await query.FirstOrDefaultAsync(cancellationToken);
if (result == null) throw new NotFoundException();
return result;
}
public async Task> FetchAsync(ISpecification specification, CancellationToken cancellationToken = default)
{
var query = ProcessQuery(specification);
if (specification.Skip.HasValue)
{
query = query.Skip((int)specification.Skip.Value);
}
if (specification.Take.HasValue)
{
query = query.Take(specification.Take.Value);
}
return await query.ToListAsync(cancellationToken);
}
public Task UpdateAsync(TEntity record, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public async Task SaveChangesAsync(CancellationToken cancellationToken = default) =>
await _db.SaveChangesAsync(cancellationToken);
private IQueryable ProcessQuery(ISpecification specification)
{
IQueryable projectedQuery;
var query = _db.Set().AsQueryable();
if (specification.WhereExpressions.Any())
{
foreach (var whereExpression in specification.WhereExpressions)
{
query = query.Where(whereExpression);
}
}
if (specification.IncludeExpressions.Any())
{
foreach (var includeExpression in specification.IncludeExpressions)
{
query = query.Include(includeExpression);
}
}
if (specification.SelectExpression != null)
{
projectedQuery = query.Select(specification.SelectExpression);
} else
{
projectedQuery = (IQueryable)query;
}
if (specification.OrderByExpressions.Any())
{
for (int i = 0; i < specification.OrderByExpressions.Count(); i++)
{
var orderByExpression = specification.OrderByExpressions.ElementAt(i);
if (i == 0) projectedQuery = projectedQuery.OrderBy(orderByExpression);
else projectedQuery = ((IOrderedQueryable)projectedQuery).ThenBy(orderByExpression);
}
}
return projectedQuery;
}
}
```
## How to run
- Run the DB Server container:
```sh
$ ./src/scripts/db.sh
```
- Run Create Database script
```sh
$ ./src/scripts/database.sh
```
- Set user secret from "API" and "Infra.Migration" projects. To do that run the following command inside `src/SpecificationPattern.Api` **and** `src/SpecificationPattern.Infra.Migration` directory:
```sh
$ dotnet user-secrets set "ConnectionStrings:ApplicationContext" "Host=localhost;Database=WineStore;Username=postgres;Password=mysecretpassword;"
```
- Restore tools. Will restore de Entity Framework Core CLI to run migrations:
```sh
$ dotnet tool restore
```
- Run the migrations command inside `src/SpecificationPattern.Infra.Migration`directory. Will create the database schema:
```sh
$ dotnet ef database update
```
- Run the API project
## Dependencies
- [.NET 6](https://dotnet.microsoft.com/en-us/download)
- [Docker](https://docs.docker.com/get-docker/) (or [Rancher Desktop](https://rancherdesktop.io) running `dockerd`)