Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

通用查询设计思想

lex-wu 2019-02-18 11:33:00 阅读数:161 评论数:0 点赞数:0 收藏数:0

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

为简单易懂,我这里简单创建一个产品类Product来说明:public classProduct {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 enumStatus { //在售 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... 来拼装条件进行查询,试想一下,如果查询条件很多的话,那我们岂不是要写很长的代码?这种流水式的代码正是我们要避免的。如何抽象化实现我们需要的功能呢?抓住我们开头说的重点,无非就是通过代码生成我们想要的表达式即可。如何生成,首先我们定义一个查询接口和它的实现///

///定义查询参数/// /// 要查询的实体类型 public interface IQuery where TEntity : class{/// ///获取查询条件/// Expression>GenerateExpression(); }/// ///定义查询参数/// /// 要查询的实体类型 public class Query : IQuery where TEntity : class{/// ///指定查询条件/// protected Expression>expression;/// ///创建一个新的 /// publicQuery() { }/// ///创建一个指定查询条件的 /// /// 指定的查询条件 public Query(Expression>expression) { expression=expression; }/// ///获取查询条件/// public virtual Expression>GenerateExpression() {return _expression.And(this.GenerateQueryExpression());}}

  我们这个接口主要作用是对TEntity的属性生成想要的表达式,来看核心的GenerateQueryExpression方法实现         

*/// 

 生成查询表达式  **/// 要查询的实体类型 public static Expression> GenerateQueryExpression(this IQueryquery)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 inqueryType.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()) {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>(body, param);return null; }/// ///生成对应的表达式/// private static Expression CreateQueryExpression(Expression param, object value, string[] propertyPath, QueryCompare compare) {var member =CreatePropertyExpression(param, propertyPath);switch(compare) {caseQueryCompare.Equal:returnCreateEqualExpression(member, value);caseQueryCompare.NotEqual:returnCreateNotEqualExpression(member, value);caseQueryCompare.Like:returnCreateLikeExpression(member, value);caseQueryCompare.NotLike:returnCreateNotLikeExpression(member, value);caseQueryCompare.StartWidth:returnCreateStartsWithExpression(member, value);caseQueryCompare.LessThan:returnCreateLessThanExpression(member, value);caseQueryCompare.LessThanOrEqual:returnCreateLessThanOrEqualExpression(member, value);caseQueryCompare.GreaterThan:returnCreateGreaterThanExpression(member, value);caseQueryCompare.GreaterThanOrEqual:returnCreateGreaterThanOrEqualExpression(member, value);caseQueryCompare.Between:returnCreateBetweenExpression(member, value);caseQueryCompare.GreaterEqualAndLess:returnCreateGreaterEqualAndLessExpression(member, value);caseQueryCompare.Include:returnCreateIncludeExpression(member, value);caseQueryCompare.NotInclude:returnCreateNotIncludeExpression(member, value);caseQueryCompare.IsNull:returnCreateIsNullExpression(member, value);caseQueryCompare.HasFlag:returnCreateHasFlagExpression(member, value);default:return null; } } /// ///生成MemberExpression/// private static MemberExpression CreatePropertyExpression(Expression param, string[] propertyPath) {var expression = propertyPath.Aggregate(param, Expression.Property) asMemberExpression;returnexpression; } /// ///生成等于的表达式/// private static Expression CreateEqualExpression(MemberExpression member, objectvalue) {if (value == null) return null;var val =Expression.Constant(ChangeType(value, member.Type), member.Type);returnExpression.Equal(member, val); } /// ///生成Sql中的like(contain)表达式 /// private static Expression CreateLikeExpression(MemberExpression member, objectvalue) {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来获取需要比较的属性和比较方法,看一下它的定义///

///查询字段/// [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]public classQueryModeAttribute : Attribute {/// ///比较方式/// public QueryCompare Compare { get; set; }/// ///对应属性路径/// public string[] PropertyPath { get; set; }/// ///查询字段/// public QueryModeAttribute(params string[] propertyPath) { PropertyPath=propertyPath; }/// ///查询字段/// public QueryModeAttribute(QueryCompare compare, params string[] propertyPath) { PropertyPath=propertyPath; Compare=compare; } }/// ///查询比较方式/// public enumQueryCompare {/// ///等于/// [Display(Name = "等于")] Equal,/// ///不等于/// [Display(Name = "不等于")] NotEqual,/// ///模糊匹配/// [Display(Name = "模糊匹配")] Like,/// ///不包含模糊匹配/// [Display(Name = "不包含模糊匹配")] NotLike,/// ///以...开头/// [Display(Name = "以...开头")] StartWidth,/// ///小于/// [Display(Name = "小于")] LessThan,/// ///小于等于/// [Display(Name = "小于等于")] LessThanOrEqual,/// ///大于/// [Display(Name = "大于")] GreaterThan,/// ///大于等于/// [Display(Name = "大于等于")] GreaterThanOrEqual,/// ///在...之间,属性必须是一个集合(或逗号分隔的字符串),取第一和最后一个值。/// [Display(Name = "在...之间")] Between,/// ///大于等于起始,小于结束,属性必须是一个集合(或逗号分隔的字符串),取第一和最后一个值。/// [Display(Name = "大于等于起始,小于结束")] GreaterEqualAndLess,/// ///包含,属性必须是一个集合(或逗号分隔的字符串)/// [Display(Name = "包含")] Include,/// ///不包含,属性必须是一个集合(或逗号分隔的字符串)/// [Display(Name = "不包含")] NotInclude,/// ///为空或不为空,可以为 bool类型,或可空类型。/// [Display(Name = "为空或不为空")] IsNull,/// ///是否包含指定枚举/// [Display(Name = "是否包含指定枚举")] HasFlag, }

  好的,那我们如何使用呢?很简单,只需在我们的查询类中继承并且指定通过何种方式比较和比较的是哪个属性即可

public class ProductQuery : Query{ //指定查询的属性是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方法

///

///查询指定条件的数据/// /// /// /// public static IQueryable Where(this IQueryable source, IQueryquery)where TEntity : class{//获取表达式var filter = query?.GenerateExpression();if (filter != null) source=source.Where(filter);returnsource; }

  这样在我们的Controller里面这样写

[HttpPost]public async TaskSearchProductList(ProductQuery query) {var data = await_productService.GetSpecifyProductListAsync(query);returnJson(result); }

我们的service层这样实现GetSpecifyProductListAsync

///

///获取指定条件的商品/// /// /// /// public Task> GetSpecifyProductListAsync(IQuery query = null) {return_productRepository.AsNoTracking().Where(query).ToListAsync(); }

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

 

总结一下:

  1. 创建我们的查询实体(ProductQuery),指定我们的查询属性(Name, Status...)和查询条件(QueryCompare)
  2. 继承我们的查询实体Query,并且指定该次查询是针对哪个数据实体(Query
  3. 封装基于Linq的方法Where方法,这里调用我们的核心方法GenerateExpression生成表达式

 

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

版权声明
本文为[lex-wu]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/lex-wu/p/10389236.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领