调整表达式目录树实现并发布到NuGet
上一篇:【NetCore】使用表达式目录树实现动态组装Where的Linq表达式
代码仓库:https://gitee.com/wosperry/perry-expression-practice.git
如果想调试的话,可以安装git后,在控制台运行 git clone https://gitee.com/wosperry/perry-expression-practice.git
获取到代码,直接运行控制台程序即可(类库项目即是NuGet的那个代码)。
上一篇使用表达式目录树实现根据参数类的配置,动态构建Lambda表达式。后面在老张的哲学弄的那个技术群里,群友给建议,让我把枚举值去掉,换成多个Attribute,仔细一想感觉是很有道理。我自己用的时候我也不希望写个特性还非要带一个要指定变量名的又臭又长的参数。
于是就着手去改。花了两个小时,把这些改完了并第一次尝试发布到NuGet,然后把控制台程序修改为安装NuGet包,一次成功,蛮开心的。同时为了配置NuGet里的几个参数,顺便把Gitee项目设置为了Apache2.0开源了。
已经发布到NuGet,安装命令
# 程序包管理控制台
Install-Package Wosperry.ExpressionExtensions -Version 1.0.2
# .Net CLI
dotnet add package Wosperry.ExpressionExtensions --version 1.0.2
# PackageReference XML
<PackageReference Include="Wosperry.ExpressionExtensions" Version="1.0.2" />
使用参考
- 实体类和参数类:
using Wosperry.ExpressionExtensions.Attributes;
namespace ConsoleApp
{
public class Student
{
public string Name { get; set; }
public string Code { get; set; }
public int? Age { get; set; }
}
public class StudentListQueryParameter
{
[WhereLike]
public string Name { get; set; }
[WhereLike("Code")]
public string Number { get; set; }
[WhereEqual]
public int? Age { get; set; }
}
}
- 使用
// 需要引用: using Wosperry.ExpressionExtensions.Extends;
#region 假数据
var input = new StudentListQueryParameter()
{
//Age = 18,
Number = "B00",
// Name = "张"
};
List<Student> students = new List<Student> {
new Student{ Code="A001",Name="张三",Age=25},
new Student{ Code="A002",Name="李四",Age=18},
new Student{ Code="A003",Name="王五",Age=10},
new Student{ Code="B001",Name="张四",Age=18},
new Student{ Code="B002",Name="李五",Age=12},
new Student{ Code="B003",Name="王六",Age=45},
new Student{ Code="C001",Name="张五",Age=22},
new Student{ Code="C001",Name="李六",Age=18},
new Student{ Code="C001",Name="王七",Age=20},
};
#endregion
// 使用
var query = students.AsQueryable();
var data = query.Where(query.BuildLambda(input)).ToList();
// 如果是ABP的IRepository, 默认实现了IQueryable,所以调用如:
// var query = _repository.Where(_repository.BuildLambda(input));
// var data = await AsyncExecuter.ToListAsync(query);
展示一下调整后的代码,同样是希望能够帮助到未来的自己,还有暂时没有接触过这个东西的同行们。
事实上代码没有那么多,只是注释太密集了,没办法,我怕过两天我自己看不懂,多写了点注释,如果不想看那么多注释,可以拷贝出去删掉看。
public static class BinaryExpressionExtensions
{
/// <summary>
/// 连接一个Like(字符串的Contains)
/// </summary>
/// <param name="resultExpression">表达式</param>
/// <param name="field">实体属性/字段</param>
/// <param name="valueToCompare">对比的值</param>
/// <param name="stringComparison">字符串对比规则</param>
/// <returns> 返回示例: xxx && t.Contains(valueToCompare)</returns>
public static BinaryExpression AndLikeLambda(this BinaryExpression resultExpression, MemberExpression field, ConstantExpression valueToCompare, StringComparison stringComparison = default)
{
// Contains 方法
var containMethod = typeof(string).GetMethod(
nameof(string.Contains),
new Type[] { typeof(string), typeof(StringComparison) }
);
// 表达式:Contains 方法
var containMethodExpression = Expression.Call(
field,
containMethod,
valueToCompare,
Expression.Constant(stringComparison)
);
return Expression.AndAlso(resultExpression, containMethodExpression);
}
/// <summary>
/// 连接一个 Equals
/// </summary>
/// <param name="resultExpression"></param>
/// <param name="field"></param>
/// <param name="valueToCompare"></param>
/// <returns></returns>
public static BinaryExpression AndEqualLambda(this BinaryExpression resultExpression, MemberExpression field, ConstantExpression valueToCompare)
{
return Expression.AndAlso(
resultExpression,
Expression.Equal(
field,
Expression.Convert(valueToCompare, field.Type)
)
);
}
}
public static class IQuaryableExtension
{
/// <summary>
/// 生成查询条件
/// </summary>
/// <typeparam name="TQueryParams">查询参数对象</typeparam>
/// <param name="queryable">原有IQueryable</param>
/// <exception cref="WhereAttribute.Field">
/// 当 WhereAttribute.Field 指定的字段名在TEntity类型中不存在的时候,抛出异常
/// </exception>
/// <returns>lambda 表达式</returns>
public static Expression<Func<TEntity, bool>> BuildLambda<TEntity, TQueryParams>(this IQueryable<TEntity> query, TQueryParams input, Action<BuildWhereOptions> options = null) where TEntity : class
{
// 配置项
BuildWhereOptions buildWhereOptions = new BuildWhereOptions();
if (!(options is null))
{
options(buildWhereOptions);
}
var t = Expression.Parameter(typeof(TEntity), "t");
// 表达式:true
var trueExpression = Expression.Constant(true);
// 表达式:true && true
var result = Expression.AndAlso(trueExpression, trueExpression);
// 遍历入参所有属性
foreach (var prop in typeof(TQueryParams).GetProperties())
{
// 根据特性分别处理
var attrs = prop.GetCustomAttributes();
// 如果没有任何特性,则使用默认 DefaultAttribute
foreach (WhereAttribute attribute in attrs.Where(w => w is WhereAttribute))
{
// 表达式:实体属性 t
// 当attr未指定 Field 值的时候,默认为与入参同名
// 党attr指定了 Field 值的时候,使用 Field
if (string.IsNullOrWhiteSpace(attribute.Field))
{
attribute.Field = prop.Name;
}
// 对比的参数为空时,不做处理
var valueToCompare = prop.GetValue(input);
if (valueToCompare is null)
continue;
// string 时,单独判断一次是否是空值
if (prop.PropertyType == typeof(string) && string.IsNullOrWhiteSpace(valueToCompare as string))
continue;
// 表达式实体属性:t.Name
var fieldExpression = Expression.Property(t, attribute.Field);
// 需要对比的值:value
var valueExpression = Expression.Constant(valueToCompare, prop.PropertyType);
#region 构建表达式
// 相等
// t.Name.Equals(value)
if (attribute is WhereEqualAttribute)
{
result = result.AndEqualLambda(fieldExpression, valueExpression);
}
// Like(字符串的Contains)
// t.Name.Contains(value, builderWhereOptions.StringComparison)
if (attribute is WhereLikeAttribute)
{
result = result.AndLikeLambda(fieldExpression, valueExpression, buildWhereOptions.StringComparison);
}
// TODO:增加其他的对比类型(Larger、Less、In)
#endregion
}
}
// 如:t=>t.Age.Equals(input.Age) && t.Code.Contains(input.Number) && t.Name.Contains(input.Name)
// 注意:input.Number是因为其有特性 [WhereLike("Code")]
return Expression.Lambda<Func<TEntity, bool>>(result, t);
}
}