EF Core 规范模式添加所有列以使用自定义规范对数据进行排序
EF Core Specification pattern add all column for sorting data with custom specification
我为我的 .net 核心项目应用了规范模式。
我还为包含、排序、分页等创建了自定义规范
我使用 queryString 从 api url 获得 sort
值并传递给自定义规范 class。
在此 class 中,我添加了一些 switch case
以确定哪一列应 orderBy
或 orderByDescending
但是 table 中的列太多了。那么有什么方法可以将 sort
变量一次应用于所有列?还是我必须将所有列都写入 switch
?
这是我的自定义规格 class。
public class PersonsWithGroupsAndPrivileges : BaseSpecification<Person>
{
public PersonsWithGroupsAndPrivileges(string sort) : base()
{
AddInclude(x => x.Group);
AddInclude(x => x.Privilege);
if(!string.IsNullOrEmpty(sort))
{
switch (sort)
{
case "gender": ApplyOrderBy(x => x.Gender); break;
case "genderDesc": ApplyOrderByDescending(x => x.Gender); break;
case "roomNo": ApplyOrderBy(x => x.RoomNo); break;
case "roomNoDesc": ApplyOrderByDescending(x => x.RoomNo); break;
default: ApplyOrderBy(x => x.Name); break;
}
}
}
}
ISpecification.cs 界面
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace XXXX.Core.Specifications
{
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; }
}
}
BaseSpecification.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace XXXX.Core.Specifications
{
public abstract class BaseSpecification<T> : ISpecification<T>
{
protected BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
protected BaseSpecification()
{
}
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 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;
}
protected virtual void ApplyGroupBy(Expression<Func<T, object>> groupByExpression)
{
GroupBy = groupByExpression;
}
}
}
SpecificationEvaluator.cs
using System.Linq;
using DesTech.Core.Entities;
using DesTech.Core.Specifications;
using Microsoft.EntityFrameworkCore;
namespace XXXX.Infrastructure.Data
{
public class SpecificationEvaluator<TEntity> where TEntity : BaseEntity
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> specification)
{
var query = inputQuery;
if (specification.Criteria != null)
{
query = query.Where(specification.Criteria);
}
query = specification.Includes.Aggregate(query, (current, include) => current.Include(include));
query = specification.IncludeStrings.Aggregate(query, (current, include) => current.Include(include));
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);
}
if (specification.IsPagingEnabled)
{
query = query.Skip(specification.Skip)
.Take(specification.Take);
}
return query;
}
}
}
一个简单的方法是使用反射按名称获取 属性,然后构建 Expression<Func<PersonWithGroupsAndPrivileges, object>>
表达式。
让我们假设这样一个规范 class:
public class BaseSpecification<T>
{
public Expression<Func<T, object>> OrderBy { get; private set; }
public Expression<Func<T, object>> OrderByDescending { get; private set; }
protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression)
=> OrderBy = orderByExpression;
protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression)
=> OrderByDescending = orderByDescendingExpression;
}
这是一个完整的示例控制台项目,它实现了 PersonWithGroupsAndPrivileges<T>
class 并将其用于 Person
class:
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace IssueConsoleTemplate
{
public class Person
{
public int PersonId { get; set; }
public string Gender { get; set; }
public string RoomNo { get; set; }
}
public class BaseSpecification<T>
{
public Expression<Func<T, object>> OrderBy { get; private set; }
public Expression<Func<T, object>> OrderByDescending { get; private set; }
protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression)
=> OrderBy = orderByExpression;
protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression)
=> OrderByDescending = orderByDescendingExpression;
protected void ApplySorting(string sort)
{
if(!string.IsNullOrEmpty(sort))
{
const string descendingSuffix = "Desc";
var descending = sort.EndsWith(descendingSuffix, StringComparison.Ordinal);
var propertyName = sort.Substring(0, 1).ToUpperInvariant() +
sort.Substring(1, sort.Length - 1 - (descending ? descendingSuffix.Length : 0));
var specificationType = GetType().BaseType;
var targetType = specificationType.GenericTypeArguments[0];
var property = targetType.GetRuntimeProperty(propertyName) ??
throw new InvalidOperationException($"Because the property {propertyName} does not exist it cannot be sorted.");
// Create an Expression<Func<T, object>>.
var lambdaParamX = Expression.Parameter(targetType, "x");
var propertyReturningExpression = Expression.Lambda(
Expression.Convert(
Expression.Property(lambdaParamX, property),
typeof(object)),
lambdaParamX);
if (descending)
{
specificationType.GetMethod(
nameof(ApplyOrderByDescending),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Invoke(this, new object[]{propertyReturningExpression});
}
else
{
specificationType.GetMethod(
nameof(ApplyOrderBy),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Invoke(this, new object[]{propertyReturningExpression});
}
}
}
}
public class PersonsWithGroupsAndPrivileges<T> : BaseSpecification<T>
{
public PersonsWithGroupsAndPrivileges(string sort)
{
ApplySorting(sort);
}
}
internal static class Program
{
private static void Main()
{
var p1 = new PersonsWithGroupsAndPrivileges<Person>("gender");
var p2 = new PersonsWithGroupsAndPrivileges<Person>("genderDesc");
}
}
}
代码适用于任何 class T
.
您基本上需要两个辅助方法 - 一个从排序字符串中提取排序信息(名称和降序),另一个动态构建并从中应用 Expression<Func<T, object>>
。两者都转到基础泛型 class.
第一个处理像 {property}[[ ]{Desc}]
(不区分大小写)的模式可能是这样的:
protected virtual void ExtractSortInfo(string sort, out string propertyPath, out bool descending)
{
const string Desc = "Desc";
propertyPath = sort;
descending = false;
if (propertyPath.EndsWith(Desc, StringComparison.OrdinalIgnoreCase))
{
propertyPath = sort.Substring(0, sort.Length - Desc.Length).TrimEnd();
descending = true;
}
}
第二个像这样:
public virtual void ApplySort(string sort)
{
if (string.IsNullOrEmpty(sort)) return;
ExtractSortInfo(sort, out var propertyPath, out var descending);
var parameter = Expression.Parameter(typeof(T), "x");
var body = propertyPath.Split('.').Aggregate((Expression)parameter, Expression.Property);
if (body.Type.IsValueType) body = Expression.Convert(body, typeof(object));
var selector = Expression.Lambda<Func<T, object>>(body, parameter);
if (descending)
ApplyOrderByDescending(selector);
else
ApplyOrderBy(selector);
}
另外,它支持点分隔的嵌套属性,例如 blog.name
for class Person
having navigation 属性 Blog
having 属性 Name
.
Expression.Convert
用于支持值类型(int
、decimal
、DateTime
等)属性
我为我的 .net 核心项目应用了规范模式。 我还为包含、排序、分页等创建了自定义规范
我使用 queryString 从 api url 获得 sort
值并传递给自定义规范 class。
在此 class 中,我添加了一些 switch case
以确定哪一列应 orderBy
或 orderByDescending
但是 table 中的列太多了。那么有什么方法可以将 sort
变量一次应用于所有列?还是我必须将所有列都写入 switch
?
这是我的自定义规格 class。
public class PersonsWithGroupsAndPrivileges : BaseSpecification<Person>
{
public PersonsWithGroupsAndPrivileges(string sort) : base()
{
AddInclude(x => x.Group);
AddInclude(x => x.Privilege);
if(!string.IsNullOrEmpty(sort))
{
switch (sort)
{
case "gender": ApplyOrderBy(x => x.Gender); break;
case "genderDesc": ApplyOrderByDescending(x => x.Gender); break;
case "roomNo": ApplyOrderBy(x => x.RoomNo); break;
case "roomNoDesc": ApplyOrderByDescending(x => x.RoomNo); break;
default: ApplyOrderBy(x => x.Name); break;
}
}
}
}
ISpecification.cs 界面
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace XXXX.Core.Specifications
{
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; }
}
}
BaseSpecification.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace XXXX.Core.Specifications
{
public abstract class BaseSpecification<T> : ISpecification<T>
{
protected BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
protected BaseSpecification()
{
}
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 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;
}
protected virtual void ApplyGroupBy(Expression<Func<T, object>> groupByExpression)
{
GroupBy = groupByExpression;
}
}
}
SpecificationEvaluator.cs
using System.Linq;
using DesTech.Core.Entities;
using DesTech.Core.Specifications;
using Microsoft.EntityFrameworkCore;
namespace XXXX.Infrastructure.Data
{
public class SpecificationEvaluator<TEntity> where TEntity : BaseEntity
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> specification)
{
var query = inputQuery;
if (specification.Criteria != null)
{
query = query.Where(specification.Criteria);
}
query = specification.Includes.Aggregate(query, (current, include) => current.Include(include));
query = specification.IncludeStrings.Aggregate(query, (current, include) => current.Include(include));
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);
}
if (specification.IsPagingEnabled)
{
query = query.Skip(specification.Skip)
.Take(specification.Take);
}
return query;
}
}
}
一个简单的方法是使用反射按名称获取 属性,然后构建 Expression<Func<PersonWithGroupsAndPrivileges, object>>
表达式。
让我们假设这样一个规范 class:
public class BaseSpecification<T>
{
public Expression<Func<T, object>> OrderBy { get; private set; }
public Expression<Func<T, object>> OrderByDescending { get; private set; }
protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression)
=> OrderBy = orderByExpression;
protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression)
=> OrderByDescending = orderByDescendingExpression;
}
这是一个完整的示例控制台项目,它实现了 PersonWithGroupsAndPrivileges<T>
class 并将其用于 Person
class:
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace IssueConsoleTemplate
{
public class Person
{
public int PersonId { get; set; }
public string Gender { get; set; }
public string RoomNo { get; set; }
}
public class BaseSpecification<T>
{
public Expression<Func<T, object>> OrderBy { get; private set; }
public Expression<Func<T, object>> OrderByDescending { get; private set; }
protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression)
=> OrderBy = orderByExpression;
protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression)
=> OrderByDescending = orderByDescendingExpression;
protected void ApplySorting(string sort)
{
if(!string.IsNullOrEmpty(sort))
{
const string descendingSuffix = "Desc";
var descending = sort.EndsWith(descendingSuffix, StringComparison.Ordinal);
var propertyName = sort.Substring(0, 1).ToUpperInvariant() +
sort.Substring(1, sort.Length - 1 - (descending ? descendingSuffix.Length : 0));
var specificationType = GetType().BaseType;
var targetType = specificationType.GenericTypeArguments[0];
var property = targetType.GetRuntimeProperty(propertyName) ??
throw new InvalidOperationException($"Because the property {propertyName} does not exist it cannot be sorted.");
// Create an Expression<Func<T, object>>.
var lambdaParamX = Expression.Parameter(targetType, "x");
var propertyReturningExpression = Expression.Lambda(
Expression.Convert(
Expression.Property(lambdaParamX, property),
typeof(object)),
lambdaParamX);
if (descending)
{
specificationType.GetMethod(
nameof(ApplyOrderByDescending),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Invoke(this, new object[]{propertyReturningExpression});
}
else
{
specificationType.GetMethod(
nameof(ApplyOrderBy),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Invoke(this, new object[]{propertyReturningExpression});
}
}
}
}
public class PersonsWithGroupsAndPrivileges<T> : BaseSpecification<T>
{
public PersonsWithGroupsAndPrivileges(string sort)
{
ApplySorting(sort);
}
}
internal static class Program
{
private static void Main()
{
var p1 = new PersonsWithGroupsAndPrivileges<Person>("gender");
var p2 = new PersonsWithGroupsAndPrivileges<Person>("genderDesc");
}
}
}
代码适用于任何 class T
.
您基本上需要两个辅助方法 - 一个从排序字符串中提取排序信息(名称和降序),另一个动态构建并从中应用 Expression<Func<T, object>>
。两者都转到基础泛型 class.
第一个处理像 {property}[[ ]{Desc}]
(不区分大小写)的模式可能是这样的:
protected virtual void ExtractSortInfo(string sort, out string propertyPath, out bool descending)
{
const string Desc = "Desc";
propertyPath = sort;
descending = false;
if (propertyPath.EndsWith(Desc, StringComparison.OrdinalIgnoreCase))
{
propertyPath = sort.Substring(0, sort.Length - Desc.Length).TrimEnd();
descending = true;
}
}
第二个像这样:
public virtual void ApplySort(string sort)
{
if (string.IsNullOrEmpty(sort)) return;
ExtractSortInfo(sort, out var propertyPath, out var descending);
var parameter = Expression.Parameter(typeof(T), "x");
var body = propertyPath.Split('.').Aggregate((Expression)parameter, Expression.Property);
if (body.Type.IsValueType) body = Expression.Convert(body, typeof(object));
var selector = Expression.Lambda<Func<T, object>>(body, parameter);
if (descending)
ApplyOrderByDescending(selector);
else
ApplyOrderBy(selector);
}
另外,它支持点分隔的嵌套属性,例如 blog.name
for class Person
having navigation 属性 Blog
having 属性 Name
.
Expression.Convert
用于支持值类型(int
、decimal
、DateTime
等)属性