分享一个Linq和Expression的通用查询设计思想[通俗易懂]

分享一个Linq和Expression的通用查询设计思想[通俗易懂]一般来说所有的系统都离不开查询,系统的查询无非都是通过实体的属性作为条件进行查询,那我们有什么方法可以拼装成类似sql中的where条件呢?在.

一般来说所有的系统都离不开查询,系统的查询无非都是通过实体的属性作为条件进行查询,那我们有什么方法可以拼装成类似sql中的where条件呢?在.Net的体系中,借助Linq + 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生成表达式

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

如果你觉得该文章还不错且有所收获,请转发分享一下!也可留下您的问题或建议,让我们共同进步!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/12059.html

(0)

相关推荐

  • 详谈 MySQL 8.0 原子 DDL 原理[通俗易懂]

    详谈 MySQL 8.0 原子 DDL 原理[通俗易懂]柯煜昌 青云科技研发顾问级工程师 目前从事 RadonDB 容器化研发,华中科技大学研究生毕业,有多年的数据库内核开发经验。 文章字数 3800+,阅读时间 15 分钟 背景 MySQL 5.7 的字

    2023-06-05
    133
  • mysql5.7.32解压版安装_mysql5.7安装教程

    mysql5.7.32解压版安装_mysql5.7安装教程下载 mysql 的 解压版安装文件mysql-5.7.25-winx64.zip D:mysqlmaster D:mysqlsalve 分别新建 data 和 binlog 目录,以及配置文件 m…

    2023-02-12
    146
  • Python程序员: 用代码实现你想要的功能

    Python程序员: 用代码实现你想要的功能随着互联网的发展,代码已经渐渐成为了一个人在信息时代中的必备技能之一。而Python作为一种简单易学、功能丰富的编程语言,已经成为了各行各业中广泛使用的工具。Python程序员可以用代码实现各种各样的功能:爬取网页数据、解析数据、制作数据可视化图表等等。Python程序员可以做的事情多种多样,本文将介绍几个常见的方面。

    2024-02-11
    99
  • Python os.path.filename实现文件路径获取文件名

    Python os.path.filename实现文件路径获取文件名在Python编程中,经常需要操作文件。而文件操作中,获取文件名是很常见的需求。Python的os.path模块提供了一系列函数,可以帮助我们实现获取文件名的操作。其中,os.path.filename()函数是专门用于获取文件名的函数。本文将着重介绍该函数及其用法。

    2024-02-29
    99
  • Python入门(四)动态网页分析及抓取[通俗易懂]

    Python入门(四)动态网页分析及抓取[通俗易懂]什么是动态网页?动态网页,就是网页中包含通过异步ajax加载出来的内容! 我们在打开某个网页时,点击右键“查看网页源代码”,会发现有一部分网页上显示的内容,源代码里面没有,而这部分就是通过ajax异步

    2023-08-08
    120
  • javascript_js知识点总结

    javascript_js知识点总结JavaScript 作为一门动态语言,和其他语言有较大的差异,这也造成很多人学习 JavaScript 时会觉得难学。但你现在看看此文,虽然是一个简略的总结,但 JavaScript 主要的内容就这些了,所以不要被自己吓到了。

    2023-08-05
    130
  • 【2019年8月】OCP 071认证考试最新版本的考试原题-第22题「建议收藏」

    【2019年8月】OCP 071认证考试最新版本的考试原题-第22题「建议收藏」Choose two Which two statements are true about transactions in the Oracle Database serve? A) An unc…

    2022-12-15
    212
  • Python 缩进规则

    Python 缩进规则Python 是一门解释性语言,在语言设计方面采用了缩进的方式来指示代码块。Python 应该是最注重缩进风格的语言之一,没有之一。Python 代码的缩进不仅是语法的一部分,还能够让代码具有更好的可读性,这也是 Python 能够成为首选编程语言之一的原因之一。

    2024-06-14
    63

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注