学习自B站杨旭老师asp.net core 3.0 web api 视频
老师B站名字(solenovex)
前言:普通的webapi对数据进行排序,大家肯定都会的,我之前是用的很多if else或者switch case 来进行的排序,那么这就会产生一个巨大的缺点,就是代码量较大,而且需要写很多的重复性代码。今天跟杨老师学习了一种新的排序方法,旨再可以使所有的排序复用,可以把排序方法写到辅助类中。那么,开始之前,我先给大家看一下真正需要写到serviderprovider中的内容到底有多少,就像下图看见的,只有两行用于排序。
public List<Employee> Get(string order)
{
IQueryable<Employee> query = _context.Employees.Where(e=>true);
var mappingDictionary = _propertyMappingService.GetPropertyMapping<EmployeeDto, Employee>();
var items = query.ApplySort(order, mappingDictionary);
return items.ToList();
}
- PropertyMappingService:IPropertyMappingService
·IList<IPropertyMapping> propertyMappings 例如:EmployeeDto:Employee
·PropertyMapping<TSource, TDestination>:IPropertyMapping
·Dictionary<string, PropertyMappingValue>
·PropertyMappingValue
·DestinationProperties 例如 FirstName LastName
·Revert 例如 true:Age→DateOfBirth
·GetPropertyMapping<TSource,TDestination>() 例如从EmployeeDto到Employee
上图不是实际代码,只是一个为了满足这两个要求的设想
这个服务可以帮助我们完成以上两个需求
我们先看一下为了满足上面两个需求而创建的类:
using System;
using System.Collections.Generic;
namespace SortProject.Services
{
public class PropertyMappingValue
{
public IEnumerable<string> DestinationProperties { get; set; }
public bool Revert { get; set; }
public PropertyMappingValue(IEnumerable<string> destinationProperties, bool revert = false)
{
DestinationProperties = destinationProperties ?? throw new ArgumentException(nameof(destinationProperties));
Revert = revert;
}
}
}
其中,DestinationProperties是可枚举类型 是我们为了解决像 Name:FirstName LastName这种一个DTO中的字段对应多个实体字段而安排的,
revert是为了解决 Age asc:DateOfBirth desc 这种相反顺序而安排的。
以下是属性映射服务的代码:
using System;
using System.Collections.Generic;
using System.Linq;
namespace SortProject.Services
{
public interface IPropertyMappingService
{
Dictionary<string, PropertyMappingValue> GetPropertyMapping<TSource, TDestination>();
}
public class PropertyMappingService:IPropertyMappingService
{
private Dictionary<string, PropertyMappingValue> _employeePropertyMapping = new Dictionary<string, PropertyMappingValue>(StringComparer.OrdinalIgnoreCase)
{
{ "Id", new PropertyMappingValue(new List<string>{ "Id"})},
{ "CompanyId", new PropertyMappingValue(new List<string>{ "CompanyId"})},
{ "EmployeeNo", new PropertyMappingValue(new List<string>{ "EmployeeNo"})},
{ "Name", new PropertyMappingValue(new List<string>{ "FirstName","LastName"})},
{ "Age", new PropertyMappingValue(new List<string>{ "DateOfBirth"},true)},
{ "GenderDisplay", new PropertyMappingValue(new List<string>{ "Gender"})}
};
private IList<IPropertyMapping> _propertyMappings = new List<IPropertyMapping>();
public PropertyMappingService()
{
_propertyMappings.Add(new PropertyMapping<EmployeeDto, Employee>(_employeePropertyMapping));
}
public Dictionary<string, PropertyMappingValue> GetPropertyMapping<TSource, TDestination>()
{
var matchingMapping = _propertyMappings.OfType<PropertyMapping<TSource, TDestination>>();
var propertyMappings = matchingMapping.ToList();
if (propertyMappings.Count() == 1)
{
return propertyMappings.First().MappingDictionary;
}
throw new Exception();
}
}
}
我这边只是做了一个小示例,所以接口和实现放的位置并不规范。
这个服务仔细看一下其实挺简单的,它实现的就是我们前面提到的DTO与实体之间的映射关系。我们需要把这些映射关系存储到一个类中。以便使用。
下面是这个类的代码:
using System;
using System.Collections.Generic;
namespace SortProject.Services
{
public class PropertyMapping<TSource, TDestination>:IPropertyMapping
{
public Dictionary<string, PropertyMappingValue> MappingDictionary { get; private set; }
public PropertyMapping(Dictionary<string, PropertyMappingValue> mappingDictionary)
{
MappingDictionary = mappingDictionary ?? throw new ArgumentException(nameof(mappingDictionary));
}
}
}
它实现了一个IPropertyMapping接口,它实际上是一个标记接口,里面什么都没有,作用就是为了给一些类打上标签,不然在PropertyMappingService中创建的private IList< IPropertyMapping >_propertyMappings =
new List< IPropertyMapping >();
这段代码就要改成private IList
下面是IPropertyMapping接口代码:
namespace SortProject.Services
{
public interface IPropertyMapping
{
}
}
通过上述代码,我们就可以找到了我们想找的所有映射。
那么,下面就是要写一个可以根据一个字符串(用的是字段名之间用逗号分隔的形式,如果有逆序在字段名后面用逗号隔开,例如:“id desc,age,name desc”,当然,用什么样的字符串并不重要,只要你能顺利的知道客户端想通过什么方式排序即可)和这些映射来排序的方法了。
方法代码如下图:
using SortProject.Services;
using System;
using System.Collections.Generic;
using System.Linq.Dynamic.Core;
using System.Linq;
namespace SortProject.Helpers
{
public static class IQueryableExtensions
{
public static IQueryable<T> ApplySort<T>(this IQueryable<T> source,
string order, Dictionary<string, PropertyMappingValue> mappingDictionary)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if(mappingDictionary == null)
{
throw new ArgumentNullException(nameof(mappingDictionary));
}
if (string.IsNullOrWhiteSpace(order))
{
return source;
}
var orderByAfterSplit = order.Split(",");
foreach (var orderBy in orderByAfterSplit.Reverse())
{
var trimmedOrderBy = orderBy.Trim();
var orderDescending = trimmedOrderBy.EndsWith(" desc");
var indexOfFirstSpace = trimmedOrderBy.IndexOf(" ");
var propertyName = indexOfFirstSpace == -1
? trimmedOrderBy
: trimmedOrderBy.Remove(indexOfFirstSpace);
if (!mappingDictionary.ContainsKey(propertyName))
{
throw new ArgumentNullException();
}
var propertyMappingValue = mappingDictionary[propertyName];
if (propertyMappingValue == null)
{
throw new ArgumentNullException();
}
foreach (var destinationProperty in propertyMappingValue.DestinationProperties.Reverse())
{
if (propertyMappingValue.Revert)
{
orderDescending = !orderDescending;
}
source = source.OrderBy(destinationProperty + (orderDescending ? " descending" : " ascending"));
}
}
return source;
}
}
}
可以看到,我们为IQueryable< T >写了一个扩展方法。然后就是分割字符串,判断是否倒序,找到对应的映射关系,进行排序,总体来看也是不难的一段代码。至于为什么我们要加Reverse(),是因为OrderBy()这个方法是从后往前起作用的,比如query.OrderBy(e=>e.id).OrderBy(e=>e.age),那么query会先根据age排序,再根据id排序。
方法写完之后,就可以像前言中一样,仅仅两行代码就可以实现排序了。
下面再贴一下实体代码,注入的代码就不展示了:
using System;
namespace SortProject
{
public class Employee
{
public Guid Id { get; set; }
public int CompanyId { get; set; }
public string EmployeeNo { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public DateTime DateOfBirth { get; set; }
}
public enum Gender
{
男,
女
}
public class EmployeeDto
{
public Guid Id { get; set; }
public int CompanyId { get; set; }
public string EmployeeNo { get; set; }
public string Name { get; set; }
public string GenderDisplay { get; set; }
public int Age { get; set; }
}
}