Browse Source
- updating to use Ardalis.Specifications package as it is maintained and has a more robust implementation - Removing all custom specification implementation - Updating unit testsmain
committed by
GitHub
28 changed files with 45 additions and 310 deletions
@ -1,26 +0,0 @@ |
|||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq.Expressions; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query |
|
||||
{ |
|
||||
public class IncludeAggregator<TEntity> |
|
||||
{ |
|
||||
public IncludeQuery<TEntity, TProperty> Include<TProperty>(Expression<Func<TEntity, TProperty>> selector) |
|
||||
{ |
|
||||
var visitor = new IncludeVisitor(); |
|
||||
visitor.Visit(selector); |
|
||||
|
|
||||
var pathMap = new Dictionary<IIncludeQuery, string>(); |
|
||||
var query = new IncludeQuery<TEntity, TProperty>(pathMap); |
|
||||
|
|
||||
if (!string.IsNullOrEmpty(visitor.Path)) |
|
||||
{ |
|
||||
pathMap[query] = visitor.Path; |
|
||||
} |
|
||||
|
|
||||
return query; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query |
|
||||
{ |
|
||||
public class IncludeQuery<TEntity, TPreviousProperty> : IIncludeQuery<TEntity, TPreviousProperty> |
|
||||
{ |
|
||||
public Dictionary<IIncludeQuery, string> PathMap { get; } = new Dictionary<IIncludeQuery, string>(); |
|
||||
public IncludeVisitor Visitor { get; } = new IncludeVisitor(); |
|
||||
|
|
||||
public IncludeQuery(Dictionary<IIncludeQuery, string> pathMap) |
|
||||
{ |
|
||||
PathMap = pathMap; |
|
||||
} |
|
||||
|
|
||||
public HashSet<string> Paths => PathMap.Select(x => x.Value).ToHashSet(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,66 +0,0 @@ |
|||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq.Expressions; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query |
|
||||
{ |
|
||||
public static class IncludeQueryExtensions |
|
||||
{ |
|
||||
public static IIncludeQuery<TEntity, TNewProperty> Include<TEntity, TPreviousProperty, TNewProperty>( |
|
||||
this IIncludeQuery<TEntity, TPreviousProperty> query, |
|
||||
Expression<Func<TEntity, TNewProperty>> selector) |
|
||||
{ |
|
||||
query.Visitor.Visit(selector); |
|
||||
|
|
||||
var includeQuery = new IncludeQuery<TEntity, TNewProperty>(query.PathMap); |
|
||||
query.PathMap[includeQuery] = query.Visitor.Path; |
|
||||
|
|
||||
return includeQuery; |
|
||||
} |
|
||||
|
|
||||
public static IIncludeQuery<TEntity, TNewProperty> ThenInclude<TEntity, TPreviousProperty, TNewProperty>( |
|
||||
this IIncludeQuery<TEntity, TPreviousProperty> query, |
|
||||
Expression<Func<TPreviousProperty, TNewProperty>> selector) |
|
||||
{ |
|
||||
query.Visitor.Visit(selector); |
|
||||
|
|
||||
// If the visitor did not generated a path, return a new IncludeQuery with an unmodified PathMap.
|
|
||||
if (string.IsNullOrEmpty(query.Visitor.Path)) |
|
||||
{ |
|
||||
return new IncludeQuery<TEntity, TNewProperty>(query.PathMap); |
|
||||
} |
|
||||
|
|
||||
var pathMap = query.PathMap; |
|
||||
var existingPath = pathMap[query]; |
|
||||
pathMap.Remove(query); |
|
||||
|
|
||||
var includeQuery = new IncludeQuery<TEntity, TNewProperty>(query.PathMap); |
|
||||
pathMap[includeQuery] = $"{existingPath}.{query.Visitor.Path}"; |
|
||||
|
|
||||
return includeQuery; |
|
||||
} |
|
||||
|
|
||||
public static IIncludeQuery<TEntity, TNewProperty> ThenInclude<TEntity, TPreviousProperty, TNewProperty>( |
|
||||
this IIncludeQuery<TEntity, IEnumerable<TPreviousProperty>> query, |
|
||||
Expression<Func<TPreviousProperty, TNewProperty>> selector) |
|
||||
{ |
|
||||
query.Visitor.Visit(selector); |
|
||||
|
|
||||
// If the visitor did not generated a path, return a new IncludeQuery with an unmodified PathMap.
|
|
||||
if (string.IsNullOrEmpty(query.Visitor.Path)) |
|
||||
{ |
|
||||
return new IncludeQuery<TEntity, TNewProperty>(query.PathMap); |
|
||||
} |
|
||||
|
|
||||
var pathMap = query.PathMap; |
|
||||
var existingPath = pathMap[query]; |
|
||||
pathMap.Remove(query); |
|
||||
|
|
||||
var includeQuery = new IncludeQuery<TEntity, TNewProperty>(query.PathMap); |
|
||||
pathMap[includeQuery] = $"{existingPath}.{query.Visitor.Path}"; |
|
||||
|
|
||||
return includeQuery; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,16 +0,0 @@ |
|||||
using System.Linq.Expressions; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Helpers.Query |
|
||||
{ |
|
||||
public class IncludeVisitor : ExpressionVisitor |
|
||||
{ |
|
||||
public string Path { get; private set; } = string.Empty; |
|
||||
|
|
||||
protected override Expression VisitMember(MemberExpression node) |
|
||||
{ |
|
||||
Path = string.IsNullOrEmpty(Path) ? node.Member.Name : $"{node.Member.Name}.{Path}"; |
|
||||
|
|
||||
return base.VisitMember(node); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,16 +0,0 @@ |
|||||
using Microsoft.eShopWeb.ApplicationCore.Helpers.Query; |
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces |
|
||||
{ |
|
||||
public interface IIncludeQuery |
|
||||
{ |
|
||||
Dictionary<IIncludeQuery, string> PathMap { get; } |
|
||||
IncludeVisitor Visitor { get; } |
|
||||
HashSet<string> Paths { get; } |
|
||||
} |
|
||||
|
|
||||
public interface IIncludeQuery<TEntity, out TPreviousProperty> : IIncludeQuery |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -1,20 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq.Expressions; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces |
|
||||
{ |
|
||||
public interface ISpecification<T> |
|
||||
{ |
|
||||
Expression<Func<T, bool>> Criteria { get; } |
|
||||
List<Expression<Func<T, object>>> Includes { get; } |
|
||||
List<string> IncludeStrings { get; } |
|
||||
Expression<Func<T, object>> OrderBy { get; } |
|
||||
Expression<Func<T, object>> OrderByDescending { get; } |
|
||||
Expression<Func<T, object>> GroupBy { get; } |
|
||||
|
|
||||
int Take { get; } |
|
||||
int Skip { get; } |
|
||||
bool IsPagingEnabled { get;} |
|
||||
} |
|
||||
} |
|
||||
@ -1,63 +0,0 @@ |
|||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using System; |
|
||||
using System.Linq.Expressions; |
|
||||
using System.Collections.Generic; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Helpers.Query; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.ApplicationCore.Specifications |
|
||||
{ |
|
||||
public abstract class BaseSpecification<T> : ISpecification<T> |
|
||||
{ |
|
||||
protected BaseSpecification(Expression<Func<T, bool>> criteria) |
|
||||
{ |
|
||||
Criteria = criteria; |
|
||||
} |
|
||||
public Expression<Func<T, bool>> Criteria { get; } |
|
||||
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>(); |
|
||||
public List<string> IncludeStrings { get; } = new List<string>(); |
|
||||
public Expression<Func<T, object>> OrderBy { get; private set; } |
|
||||
public Expression<Func<T, object>> OrderByDescending { get; private set; } |
|
||||
public Expression<Func<T, object>> GroupBy { get; private set; } |
|
||||
|
|
||||
public int Take { get; private set; } |
|
||||
public int Skip { get; private set; } |
|
||||
public bool IsPagingEnabled { get; private set; } = false; |
|
||||
|
|
||||
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression) |
|
||||
{ |
|
||||
Includes.Add(includeExpression); |
|
||||
} |
|
||||
|
|
||||
protected virtual void AddIncludes<TProperty>(Func<IncludeAggregator<T>, IIncludeQuery<T, TProperty>> includeGenerator) |
|
||||
{ |
|
||||
var includeQuery = includeGenerator(new IncludeAggregator<T>()); |
|
||||
IncludeStrings.AddRange(includeQuery.Paths); |
|
||||
} |
|
||||
|
|
||||
protected virtual void AddInclude(string includeString) |
|
||||
{ |
|
||||
IncludeStrings.Add(includeString); |
|
||||
} |
|
||||
protected virtual void ApplyPaging(int skip, int take) |
|
||||
{ |
|
||||
Skip = skip; |
|
||||
Take = take; |
|
||||
IsPagingEnabled = true; |
|
||||
} |
|
||||
protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression) |
|
||||
{ |
|
||||
OrderBy = orderByExpression; |
|
||||
} |
|
||||
protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression) |
|
||||
{ |
|
||||
OrderByDescending = orderByDescendingExpression; |
|
||||
} |
|
||||
|
|
||||
//Not used anywhere at the moment, but someone requested an example of setting this up.
|
|
||||
protected virtual void ApplyGroupBy(Expression<Func<T, object>> groupByExpression) |
|
||||
{ |
|
||||
GroupBy = groupByExpression; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,52 +0,0 @@ |
|||||
using Microsoft.EntityFrameworkCore; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using System.Linq; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.Infrastructure.Data |
|
||||
{ |
|
||||
public class SpecificationEvaluator<T> where T : BaseEntity |
|
||||
{ |
|
||||
public static IQueryable<T> GetQuery(IQueryable<T> inputQuery, ISpecification<T> specification) |
|
||||
{ |
|
||||
var query = inputQuery; |
|
||||
|
|
||||
// modify the IQueryable using the specification's criteria expression
|
|
||||
if (specification.Criteria != null) |
|
||||
{ |
|
||||
query = query.Where(specification.Criteria); |
|
||||
} |
|
||||
|
|
||||
// Includes all expression-based includes
|
|
||||
query = specification.Includes.Aggregate(query, |
|
||||
(current, include) => current.Include(include)); |
|
||||
|
|
||||
// Include any string-based include statements
|
|
||||
query = specification.IncludeStrings.Aggregate(query, |
|
||||
(current, include) => current.Include(include)); |
|
||||
|
|
||||
// Apply ordering if expressions are set
|
|
||||
if (specification.OrderBy != null) |
|
||||
{ |
|
||||
query = query.OrderBy(specification.OrderBy); |
|
||||
} |
|
||||
else if (specification.OrderByDescending != null) |
|
||||
{ |
|
||||
query = query.OrderByDescending(specification.OrderByDescending); |
|
||||
} |
|
||||
|
|
||||
if (specification.GroupBy != null) |
|
||||
{ |
|
||||
query = query.GroupBy(specification.GroupBy).SelectMany(x => x); |
|
||||
} |
|
||||
|
|
||||
// Apply paging if enabled
|
|
||||
if (specification.IsPagingEnabled) |
|
||||
{ |
|
||||
query = query.Skip(specification.Skip) |
|
||||
.Take(specification.Take); |
|
||||
} |
|
||||
return query; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue