一般来说所有的系统都离不开查询,通俗点说就是前后端交互。我们的系统无非都是通过实体的属性作为条件进行查询,那我们有什么方法可以拼装成类似sql中的where条件呢?在.Net的体系中,有个很好的东西叫做表达式(Expression),借助它我们可以将查询参数转化为表达式进行查询。

  为简单易懂,我这里简单创建一个产品类Product来说明:
  public class Product { public int Id {get;set;} public string Name {get;set;}
public decimal Price {get;set;} //库存 public int Stock {get;set;} public Status
Status {get;set;}      //创建时间 public DateTime CreationTime {get;set;} } public
enum Status {      //在售 OnSale = 1, //下架 OffSale = 2 }
  我们页面需要通过商品名,库存范围,状态和创建时间范围来作为条件查询指定的商品,这里我们先定义我们的查询类
  public class ProductQuery { public string Name {get;set;} //最小库存public int
MinStock {get;set;} //最大库存 public int MaxStock {get;set;} public Status Status {
get;set;}      //创建开始时间 public DateTime CreationStartTime {get;set;} //创建结束时间
public DateTime CreationEndTime {get;set;} }
 

  有了查询类,一般的思想是通过if ...else...
来拼装条件进行查询,试想一下,如果查询条件很多的话,那我们岂不是要写很长的代码?这种流水式的代码正是我们要避免的。如何抽象化实现我们需要的功能呢?抓住我们开头说的重点,无非就是通过代码生成我们想要的表达式即可。如何生成,首先我们定义一个查询接口和它的实现
   /// <summary> /// 定义查询参数 /// </summary> /// <typeparam name="TEntity">
要查询的实体类型</typeparam> public interface IQuery<TEntity> where TEntity : class {
/// <summary> /// 获取查询条件 /// </summary> Expression<Func<TEntity, bool>>
GenerateExpression(); }/// <summary> /// 定义查询参数 /// </summary> /// <typeparam
name="TEntity">要查询的实体类型</typeparam> public class Query<TEntity> :
IQuery<TEntity>where TEntity : class { /// <summary> /// 指定查询条件 /// </summary>
protected Expression<Func<TEntity, bool>> _expression; /// <summary> /// 创建一个新的
<see cref="Query{TEntity}"/> /// </summary> public Query() { } /// <summary> ///
创建一个指定查询条件的<see cref="Query{TEntity}"/> /// </summary> /// <param
name="expression">指定的查询条件</param> public Query(Expression<Func<TEntity, bool>>
expression) { _expression= expression; } /// <summary> /// 获取查询条件 /// </summary>
public virtual Expression<Func<TEntity, bool>> GenerateExpression() { return
_expression.And(this.GenerateQueryExpression());
     }
  }
   我们这个接口主要作用是对TEntity的属性生成想要的表达式,来看核心的GenerateQueryExpression方法实现         
  /// <summary> 生成查询表达式  </summary>
  /// <typeparam name="TEntity">要查询的实体类型</typeparam> public static
Expression<Func<TEntity,bool>> GenerateQueryExpression<TEntity>(this
IQuery<TEntity> query) where TEntity : class { if (query == null) return null;
var queryType = query.GetType(); var param = Expression.Parameter(typeof
(TEntity),"m"); Expression body = null; foreach (PropertyInfo property in
queryType.GetProperties()) {var value = property.GetValue(query); if (value is
string) { var str = ((string)value).Trim(); value = string.IsNullOrEmpty(str) ?
null : str; } Expression sub = null;           
          
        //针对QueryMode特性获取我们指定要查询的路径 foreach (var attribute in
property.GetAttributes<QueryModeAttribute>()) { var propertyPath =
attribute.PropertyPath;if (propertyPath == null || propertyPath.Length == 0)
propertyPath= new[] { property.Name }; var experssion = CreateQueryExpression
(param, value, propertyPath, attribute.Compare);if (experssion != null) { sub =
sub ==null ? experssion : Expression.Or(sub, experssion); } } if (sub != null)
{ body= body == null ? sub : Expression.And(body, sub); } } if (body != null)
return Expression.Lambda<Func<TEntity, bool>>(body, param); return null; } ///
<summary> /// 生成对应的表达式 /// </summary> private static Expression
CreateQueryExpression(Expression param, object value, string[] propertyPath,
QueryCompare compare) {var member = CreatePropertyExpression(param,
propertyPath);switch (compare) { case QueryCompare.Equal: return
CreateEqualExpression(member, value);case QueryCompare.NotEqual: return
CreateNotEqualExpression(member, value);case QueryCompare.Like: return
CreateLikeExpression(member, value);case QueryCompare.NotLike: return
CreateNotLikeExpression(member, value);case QueryCompare.StartWidth: return
CreateStartsWithExpression(member, value);case QueryCompare.LessThan: return
CreateLessThanExpression(member, value);case QueryCompare.LessThanOrEqual:
return CreateLessThanOrEqualExpression(member, value); case
QueryCompare.GreaterThan:return CreateGreaterThanExpression(member, value); case
QueryCompare.GreaterThanOrEqual:return
CreateGreaterThanOrEqualExpression(member, value);case QueryCompare.Between:
return CreateBetweenExpression(member, value); case
QueryCompare.GreaterEqualAndLess:return
CreateGreaterEqualAndLessExpression(member, value);case QueryCompare.Include:
return CreateIncludeExpression(member, value); case QueryCompare.NotInclude:
return CreateNotIncludeExpression(member, value); case QueryCompare.IsNull:
return CreateIsNullExpression(member, value); case QueryCompare.HasFlag: return
CreateHasFlagExpression(member, value);default: return null; } }   /// <summary>
/// 生成MemberExpression /// </summary>   private static MemberExpression
CreatePropertyExpression(Expression param,string[] propertyPath) { var
expression = propertyPath.Aggregate(param, Expression.Property)as
MemberExpression;return expression; }    /// <summary> /// 生成等于的表达式 ///
</summary>   private static Expression CreateEqualExpression(MemberExpression
member,object value) { if (value == null) return null; var val =
Expression.Constant(ChangeType(value, member.Type), member.Type);return
Expression.Equal(member, val); }   /// <summary>   /// 生成Sql中的like(contain)表达式
  /// </summary>   private static Expression
CreateLikeExpression(MemberExpression member,object value) { if (value == null)
return null; if (member.Type != typeof(string)) throw new
ArgumentOutOfRangeException(nameof(member), $"Member '{member}' can not use
'Like' compare"); var str = value.ToString(); var val =
Expression.Constant(str);return Expression.Call(member, nameof(string.Contains),
null, val); } 其他条件的表达式暂时忽略。。。。
 

   从这两个核心的方法中我们可以看出,主要是通过自定义的这个QueryModeAttribute来获取需要比较的属性和比较方法,看一下它的定义
/// <summary> /// 查询字段 /// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple =true)] public class
QueryModeAttribute : Attribute {/// <summary> /// 比较方式 /// </summary> public
QueryCompare Compare { get; set; } /// <summary> /// 对应属性路径 /// </summary>
public string[] PropertyPath { get; set; } /// <summary> /// 查询字段 /// </summary>
public QueryModeAttribute(params string[] propertyPath) { PropertyPath =
propertyPath; }/// <summary> /// 查询字段 /// </summary> public
QueryModeAttribute(QueryCompare compare,params string[] propertyPath) {
PropertyPath= propertyPath; Compare = compare; } } /// <summary> /// 查询比较方式 ///
</summary> public enum QueryCompare { /// <summary> /// 等于 /// </summary>
[Display(Name ="等于")] Equal, /// <summary> /// 不等于 /// </summary> [Display(Name
="不等于")] NotEqual, /// <summary> /// 模糊匹配 /// </summary> [Display(Name = "模糊匹配"
)] Like,/// <summary> /// 不包含模糊匹配 /// </summary> [Display(Name = "不包含模糊匹配")]
NotLike,/// <summary> /// 以...开头 /// </summary> [Display(Name = "以...开头")]
StartWidth,/// <summary> /// 小于 /// </summary> [Display(Name = "小于")] LessThan,
/// <summary> /// 小于等于 /// </summary> [Display(Name = "小于等于")] LessThanOrEqual,
/// <summary> /// 大于 /// </summary> [Display(Name = "大于")] GreaterThan, ///
<summary> /// 大于等于 /// </summary> [Display(Name = "大于等于")] GreaterThanOrEqual,
/// <summary> /// 在...之间,属性必须是一个集合(或逗号分隔的字符串),取第一和最后一个值。 /// </summary>
[Display(Name ="在...之间")] Between, /// <summary> ///
大于等于起始,小于结束,属性必须是一个集合(或逗号分隔的字符串),取第一和最后一个值。/// </summary> [Display(Name = "
大于等于起始,小于结束")] GreaterEqualAndLess, /// <summary> /// 包含,属性必须是一个集合(或逗号分隔的字符串)
/// </summary> [Display(Name = "包含")] Include, /// <summary> ///
不包含,属性必须是一个集合(或逗号分隔的字符串)/// </summary> [Display(Name = "不包含")] NotInclude, ///
<summary> /// 为空或不为空,可以为 bool类型,或可空类型。 /// </summary> [Display(Name = "为空或不为空"
)] IsNull,/// <summary> /// 是否包含指定枚举 /// </summary> [Display(Name = "是否包含指定枚举"
)] HasFlag, }
   好的,那我们如何使用呢?很简单,只需在我们的查询类中继承并且指定通过何种方式比较和比较的是哪个属性即可
  public class ProductQuery : Query<Product> {     //指定查询的属性是Name,且条件是Like
[QueryMode(QueryCompare.Like,nameof(Product.Name))]public string Name {get;set;}
//最小库存     //指定查询的属性是Stock,且条件是大于等与
[QueryMode(QueryCompare.GreaterThanOrEqual,nameof(Product.Stock))]public int
MinStock {get;set;} //最大库存      //指定查询条件是Stock,且条件是小于等于
[QueryMode(QueryCompare.LessThanOrEqual,nameof(Product.Stock))]public int
MaxStock {get;set;} //指定查询条件是Status,且条件是等于
[QueryMode(QueryCompare.Equal,nameof(Product.Status))]public Status Status {get;
set;}      //创建开始时间 //指定查询条件是CreationTime,且条件是大于等与
[QueryMode(QueryCompare.GreaterThanOrEqual,nameof(Product.CreationTime))]public
DateTime CreationStartTime {get;set;} //创建结束时间 //指定查询条件是CreationTime,且条件是小于等于
[QueryMode(QueryCompare.LessThanOrEqual,nameof(Product.CreationTime))]public
DateTime CreationEndTime {get;set;} }
  在使用Linq方法查询时,比如调用基于IQueryable的Where方法时,我们可以封装自己的Where方法
  /// <summary> /// 查询指定条件的数据 /// </summary> /// <typeparam
name="TEntity"></typeparam> /// <param name="source"></param> /// <param
name="query"></param> public static IQueryable<TEntity> Where<TEntity>(this
IQueryable<TEntity> source, IQuery<TEntity> query) where TEntity : class {
        //获取表达式 var filter = query?.GenerateExpression(); if (filter != null)
source= source.Where(filter); return source; }
   这样在我们的Controller里面这样写
  [HttpPost] public async Task<JsonResult> SearchProductList(ProductQuery
query) {var data = await _productService.GetSpecifyProductListAsync(query);
return Json(result); }
  我们的service层这样实现GetSpecifyProductListAsync
  /// <summary> /// 获取指定条件的商品 /// </summary> /// <typeparam
name="TEntity"></typeparam> /// <param name="query"></param> ///
<returns></returns>     public Task<List<Product>>
GetSpecifyProductListAsync<Product>(IQuery<Product> query =null) { return
_productRepository.AsNoTracking().Where(query).ToListAsync(); }

  这样在前端传过来的条件,都会自动通过我们核心的方法GenerateExpression生成的表达式作为条件进行查询进而返回实体列表。当然,还可以有更高级的方法,比如返回的是分页的数据,或者返回的是指定的类型(直接返回实体是不安全的),后续我们都会针对更高级的开发思想来讲解到这些情况。

 

  总结一下:

  1. 创建我们的查询实体(ProductQuery),指定我们的查询属性(Name, Status...)和查询条件(QueryCompare)

  2. 继承我们的查询实体Query,并且指定该次查询是针对哪个数据实体(Query<Product>)

  3. 封装基于Linq的方法Where方法,这里调用我们的核心方法GenerateExpression生成表达式

 

  如果有更好的想法,欢迎探讨。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信