asp.net core 3.0 web api 灵活并且可复用的排序

学习自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();
        }

一、排序遇到的问题

  • 映射:Name:FirstName + LastName
  • 应用排序。但是目前只能使用string,而不是lambda表达式
    ·System.Linq.Dynamic.Core 这个Linq扩展库
  • 复用性
    ·针对IQueryable的一个扩展方法?

二、属性映射服务

需求:

  • 一个资源(DTO)的属性可以映射到Entity上面多个属性
    ·Name:FirstName LastName
  • 映射可能需要反转顺序
    ·Age asc:DateOfBirth desc

服务:

 - 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> _propertyMappings = new List< IPropertyMapping >(); 了,但是这里面不支持泛型的解析,所以会报错。
下面是IPropertyMapping接口代码:

namespace SortProject.Services
{
    public interface IPropertyMapping
    {
    }
}

通过上述代码,我们就可以找到了我们想找的所有映射。
asp.net core 3.0 web api 灵活并且可复用的排序_第1张图片
那么,下面就是要写一个可以根据一个字符串(用的是字段名之间用逗号分隔的形式,如果有逆序在字段名后面用逗号隔开,例如:“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; }
    }
}

你可能感兴趣的:(asp.net core 3.0 web api 灵活并且可复用的排序)