Asp.Net Core 扩展 Linq,简化自定义

Asp.Net Core 扩展 Linq
实现通过属性名称来排序和查询以及分页

前言 -为什么需要扩展 Linq 方法

Linq 在 .net 中使用是比较多的,而微软开发的 linq 相关函数无法满足实际项目开发中的需求,我们需要自己来扩展一些方法。

c# 扩展方法

在 Asp.Net Core 开发中或者其他的后端开发中都会有一个需求(尤其对于中台或者后台管理),那就是展示数据列表;当然不是普普通通的数据列表展示,而是需要进行排序分页查询关键字来获取列表。

甚至在有些时候需要三个同时处理来更精确的筛选数据,而对于 Asp.Net Core 来说,用的语言是 C#,是一门强类型语言,在许多时候具有很大便利性,但是正因为这种原因,在某些时候却不太方便使用,需要进行额外方式来进行处理,才可以达到目标。

普通查询

对于 Linq 查询来说,WhereOrderBy 使用时需要直接点出来属于或者字段才行,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 数据结构
public class ArticleTag
{
public int Id { get; set; }

public Guid KeyId { get; set; }

public string Name { get; set; }

public string BgColor { get; set; }

public string TextColor { get; set; }

public string UrlParam { get; set; }

public string Remark { get; set; }

public DateTime CreatedTime { get; set; }

public DateTime UpdatedTime { get; set; }
}
1
2
3
4
5
public static void Test(){
var articleTags = new List<ArticleTag>();
// where 查找 Name中含有Admin的数据,orderby 通过 id 来进行排序
var result = articleTags.Where(p => p.Name.Contains("Admin")).OrderBy(p => p.Id);
}

而所谓的一些限制,指的就是如上所示的,在进行 where 时,是通过 . 出来属性进行查询的,但是实际使用中,从前端传递过来的一般都是字符串 "Name",而在后端进行查询时,以目前方式是无法将属性的key写到where函数中,也就无法执行查询通过"Name"来过滤数据;如果通过 if...else 来判断,那么将会是一个非常大的工程量,每个实体上面有 m 个属性,而一个项目中有 n 张表,那么几乎需要 m*n个判断进行处理,非常的差劲,不利于后续扩展和维护。

但是天无绝人之路,在 c#中拥有扩展方法、表达式目录树和反射,可以将上面的方式进行优化。

查询条件参数公共类型

先需要定义查询条件的公共参数,用于统一规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
namespace BlogSite.CommonLib.CommonEntity
{
/// <summary>
/// 查询基本实体
/// </summary>
public class CoditionEntity
{
[JsonPropertyName("paginationKeyValue")]
public PaginationKeyValue PaginationKeyValue { get; set; }

[JsonPropertyName("orderByKeyValue")]
public OrderByKeyValue OrderByKeyValue { get; set; }

[JsonPropertyName("searchKeyValue")]
public SearchKeyValue SearchKeyValue { get; set; }
}
/// <summary>
/// 关键字搜索
/// </summary>
public class SearchKeyValue
{
private string _propertyName;
[JsonPropertyName("enable")]
public bool Enable { get; set; }
[JsonPropertyName("propertyName")]
public string PropertyName
{
get
{
// 因为前端传递的是小写字母开头的,但是在后端都是大写字母开头的,我们需要在获取时将首字母大写
return _propertyName.ToUpperFirstLetter();
}
set
{
_propertyName = value;
}
}
[JsonPropertyName("searchContent")]
public string SearchContent { get; set; }
}

/// <summary>
/// 排序字段格式
/// </summary>
public class OrderByKeyValue
{
private string _propertyName;
[JsonPropertyName("enable")]
public bool Enable { get; set; }
[JsonPropertyName("propertyName")]
public string PropertyName {
get
{
return _propertyName.ToUpperFirstLetter();
}
set
{
_propertyName = value;
}
}
[JsonPropertyName("orderDirection")]
public OrderDirection OrderDirection { get; set; }
}
/// <summary>
/// 分页格式
/// </summary>
public class PaginationKeyValue
{
[JsonPropertyName("enable")]
public bool Enable { get; set; }
[JsonPropertyName("count")]
public int Count { get; set; }
[JsonPropertyName("pageSize")]
public int PageSize { get; set; }
[JsonPropertyName("pageNumber")]
public int PageNumber { get; set; }
}

/// <summary>
/// 排序类型
/// </summary>
public enum OrderDirection
{
/// <summary>
/// 升序
/// </summary>
[Description("升序")]
ASC = 0,
/// <summary>
/// 降序
/// </summary>
[Description("降序")]
DESC = 1
}
}

建立了查询条件基本结构,然后需要基于这个结构来进行处理

Linq 扩展方法

对于 Linq 扩展方法来说,需要使用到表达式目录树和反射等高级操作,本人目前对于此处理解不是太深,就越过这里了,直接说如何实现即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
public static class LinqExtension
{
/// <summary>
/// Orders the by.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <param name="direction">The direction.</param>
/// <returns></returns>
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName, OrderDirection direction)
{
switch (direction)
{
case OrderDirection.ASC:
return source.OrderBy(ToLambda<T>(propertyName));

case OrderDirection.DESC:
return source.OrderByDescending(ToLambda<T>(propertyName));

default:
return source.OrderBy(ToLambda<T>(propertyName));
}

}

/// <summary>
/// Orders the by.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <param name="direction">The direction.</param>
/// <returns></returns>
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName, int direction)
{
var dir = (OrderDirection)direction;

switch (dir)
{
case OrderDirection.ASC:
return source.OrderBy(ToLambda<T>(propertyName));

case OrderDirection.DESC:
return source.OrderByDescending(ToLambda<T>(propertyName));

default:
return source.OrderBy(ToLambda<T>(propertyName));
}

}


/// <summary>
/// Orders the by.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}

/// <summary>
/// Orders the by descending.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderByDescending(ToLambda<T>(propertyName));
}

public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, string content)
{
return source.Where(ToLambdaWhere<T>(propertyName, content));
}

public static IQueryable<T> WhereList<T>(this IQueryable<T> source, string propertyName, List<string> contentList)
{
return source.Where(ToLambdaWhere<T>(propertyName, contentList));
}

/// <summary>
/// Where Lambda 表示式
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <param name="contentList"></param>
/// <returns></returns>
private static Expression<Func<T, bool>> ToLambdaWhere<T>(string propertyName, List<string> contentList)
{
// 传递到表达式目录树的参数 x 和它的类型
ParameterExpression x = Expression.Parameter(typeof(T));

MemberExpression property = Expression.Property(x, propertyName);
ConstantExpression constant = Expression.Constant(contentList, typeof(List<string>));

var methodCall = Expression.Call(constant, "Contains", Type.EmptyTypes, property);


var expression = Expression.Lambda<Func<T, bool>>(methodCall, new ParameterExpression[] { x });
return expression;
}

/// <summary>
/// Where Lambda 表示式
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <param name="content"></param>
/// <returns></returns>
private static Expression<Func<T, bool>> ToLambdaWhere<T>(string propertyName, string content)
{
// 传递到表达式目录树的参数 x 和它的类型
ParameterExpression x = Expression.Parameter(typeof(T));
// 获取类型的属性
PropertyInfo prop = typeof(T).GetProperty(propertyName);
// 设定常量值相当于Contains中传递的值
ConstantExpression constant = Expression.Constant(content, typeof(string));
// 使用类型调用对应的属性
var propExp = Expression.Property(x, prop);
// 获取方法
MethodInfo contains = typeof(string).GetMethod("Contains", new Type[] { });
// 给属性调用上面获取的方法,传递值
var methodCall = Expression.Call(propExp, contains, new Expression[] { constant });
// 拼装成表达式目录树
Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(methodCall, new ParameterExpression[] { x });
return expression;
}

/// <summary>
/// Converts to lambda.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
// 传递到表达式目录树的参数 x
ParameterExpression x = Expression.Parameter(typeof(T));
// 通过传递过来的属性字符串获取对应的属性
MemberExpression property = Expression.Property(x, propertyName);
// 创建一个表示类型转换运算的 UnaryExpression。
// 使用 property.Type 会在最后转换时出现错误
var propAsObject = Expression.Convert(property, typeof(object));

Expression<Func<T, object>> expression = Expression.Lambda<Func<T, object>>(propAsObject, x);
return expression;
}
}

实际使用中可以直接使用上方的扩展方法,扩展方法做成后,只需要按照如下调用即可

1
2
3
4
5
public static void Test(){
var articleTags = new List<ArticleTag>();
// 将属性key字符串传递进来就可以实现查询和排序,方便使用,PropertyName可以随意更改,当PropertyName不属于查询的类型中时,只会抛出错误,需要进一步处理
var result = articleTags.Where("PropertyName","SerarchKeyword").OrderBy("PropertyName",1);
}

更进一步

当然在上面我们也定义了通用的查询条件,那么我们直接也可以再进一步扩展,来达到更好的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static class LinqMethod
{
/// <summary>
/// 使用自定linq扩展执行排序,查询,分页功能 item1: 未分页结果,item2:分页后的结果
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="coditionEntity"></param>
/// <returns></returns>
public static Tuple<IQueryable<T>, IQueryable<T>> UseCoditionFind<T>(this IQueryable<T> source, CoditionEntity coditionEntity)
{
var query = source;
var resultQuery = source;
if (coditionEntity.SearchKeyValue != null && coditionEntity.SearchKeyValue.Enable && !coditionEntity.SearchKeyValue.SearchContent.IsNullOrWhiteSpace())
{
query = query.Where(coditionEntity.SearchKeyValue.PropertyName, coditionEntity.SearchKeyValue.SearchContent);
}
if (coditionEntity.OrderByKeyValue != null && coditionEntity.OrderByKeyValue.Enable)
{
query = query.OrderBy(coditionEntity.OrderByKeyValue.PropertyName, coditionEntity.OrderByKeyValue.OrderDirection);
}
if (coditionEntity.PaginationKeyValue != null && coditionEntity.PaginationKeyValue.Enable)
{
resultQuery = query.Skip((coditionEntity.PaginationKeyValue.PageNumber - 1) * coditionEntity.PaginationKeyValue.PageSize)
.Take(coditionEntity.PaginationKeyValue.PageSize);
}
return new Tuple<IQueryable<T>, IQueryable<T>>(query, resultQuery);
}
}

这样通过 linq 就可以直接调用 UseCoditionFind 然后获取返回的 query,然后再 ToList 获取数据,返回即可。方便使用,规范查询数据

-------文章到此结束  感谢您的阅读-------