博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
应用程序框架实战二十五:查询条件(规约模式应用)
阅读量:6809 次
发布时间:2019-06-26

本文共 11907 字,大约阅读时间需要 39 分钟。

  前面已经做了一些准备工作,本篇将介绍查询条件的封装,它是规约模式的一个应用。

  规约使用一个对象来封装谓词,我之前已经介绍过它在验证方面的应用,本篇是规约模式在查询方面的应用。

  规约的强大之处在于,能够将一堆杂乱无章的条件判断或查询条件封装起来,以一个清晰的概念来表达,并使得这些谓词具备了可复用的能力。

  首先在Util.Domains项目的Repositories目录中创建ICriteria接口,这个接口表示一个查询条件,代码如下。

using System;using System.Linq.Expressions;namespace Util.Domains.Repositories {    ///     /// 查询条件    ///     /// 
实体类型
public interface ICriteria
where TEntity : class,IAggregateRoot { ///
/// 获取谓词 /// Expression
> GetPredicate(); }}

  由于我们使用了EF这种ORM框架,查询条件的结果是一个Expression<Func<TEntity, bool>>的谓词表达式。

  在Util.Datas项目中,打开Extensions.Query.cs文件,增加以下代码。

///         /// 过滤        ///         /// 
实体类型
/// 数据源 /// 查询条件 public static IQueryable
Filter
( this IQueryable
source, ICriteria
criteria ) where T : class,IAggregateRoot { if ( criteria == null ) return source; var predicate = criteria.GetPredicate(); if ( predicate == null ) return source; return source.Where( predicate ); }

  我们在IQueryable对象上扩展了一个Filter方法,该方法接收一个查询条件,如果查询条件有效,就使用Where方法添加过滤条件。

  基础工作就这么多,下面来看几个范例。

  在信息系统中,经常会进行范围查询,比如一个日期段的查询。这看起来是一个简单的需求,初学者一般这样写,t => t.Date >= BeginDate && t.Date <= EndDate,其结果可能是错误的,这是由于从表现层传入的查询条件是可选的,如果客户没有进行输入,结果就是错的。

  对于范围查询来讲,还有更多的细节需要思考,比如,起始日期和结束日期都没有输入,或只输入了起始日期或结束日期,也可能客户输入的起始日期比结束日期还大。为了获得健壮性,我们会对查询条件进行各种判断,从而导致杂乱无章的代码。更要命的是,这些代码无法复用,在另一个范围查询的位置,我们必须把之前的代码复制过去进行修改。

  一个更好的办法是把范围查询逻辑封装到查询条件对象中,以后需要进行范围查询时,即可随手拈来。

  在Util.Datas项目Queries目录中,新建Criterias目录,创建一个查询条件基类CriteriaBase,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;using Util.Domains.Repositories;namespace Util.Datas.Queries.Criterias {    ///     /// 查询条件    ///     /// 
实体类型
public abstract class CriteriaBase
: ICriteria
where TEntity : class, IAggregateRoot { ///
/// 谓词 /// protected Expression
> Predicate { get; set; } ///
/// 获取谓词 /// public virtual Expression
> GetPredicate() { return Predicate; } }}

  根据数据类型不同,范围查询有很多种类,比如日期范围查询、日期时间范围查询、整数范围查询、浮点数范围查询等。我们需要为范围查询条件创建一个基类SegmentCriteria,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;using Util.Lambdas;namespace Util.Datas.Queries.Criterias {    ///     /// 段过滤条件    ///     /// 
实体类型
///
属性类型
///
值类型
public abstract class SegmentCriteria
: CriteriaBase
where TEntity : class, IAggregateRoot where TValue : struct { ///
/// 初始化段过滤条件 /// ///
属性表达式 ///
最小值 ///
最大值 protected SegmentCriteria( Expression
> propertyExpression, TValue? min, TValue? max ) { Builder = new ExpressionBuilder
(); PropertyExpression = propertyExpression; Min = min; Max = max; if ( IsMinGreaterMax( min, max ) ) { Min = max; Max = min; } } ///
/// 最小值是否大于最大值 /// ///
最小值 ///
最大值 protected abstract bool IsMinGreaterMax( TValue? min, TValue? max ); ///
/// 属性表达式 /// public Expression
> PropertyExpression { get; set; } ///
/// 表达式生成器 /// private ExpressionBuilder
Builder { get; set; } ///
/// 最小值 /// public TValue? Min { get; set; } ///
/// 最大值 /// public TValue? Max { get; set; } ///
/// 获取谓词 /// public override Expression
> GetPredicate() { var first = CreateLeftExpression(); var second = CreateRightExpression(); return Builder.ToLambda( first.And( second ) ); } ///
/// 创建左操作数,即 t => t.Property >= Min /// private Expression CreateLeftExpression() { if ( Min == null ) return null; return Builder.Create( PropertyExpression, Operator.GreaterEqual, GetMinValue() ); } ///
/// 获取最小值 /// protected virtual TValue? GetMinValue() { return Min; } ///
/// 创建右操作数,即 t => t.Property <= Max /// private Expression CreateRightExpression() { if ( Max == null ) return null; return Builder.Create( PropertyExpression, GetMaxOperator(), GetMaxValue() ); } ///
/// 获取最大值相关的运算符 /// protected virtual Operator GetMaxOperator() { return Operator.LessEqual; } ///
/// 获取最大值 /// protected virtual TValue? GetMaxValue() { return Max; } }}

  对于日期范围查询,日期是否包含时间非常重要,它们在行为上是不同的。如果日期不包含时间,那么需要为结束日期加一天,并修改运算符为小于。

  日期时间范围查询条件DateTimeSegmentCriteria,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;namespace Util.Datas.Queries.Criterias {    ///     /// 日期时间段过滤条件 - 包含时间    ///     /// 
实体类型
///
属性类型
public class DateTimeSegmentCriteria
: SegmentCriteria
where TEntity : class,IAggregateRoot { ///
/// 初始化日期时间段过滤条件 /// ///
属性表达式 ///
最小值 ///
最大值 public DateTimeSegmentCriteria( Expression
> propertyExpression, DateTime? min, DateTime? max ) : base( propertyExpression, min, max ) { } ///
/// 最小值是否大于最大值 /// protected override bool IsMinGreaterMax( DateTime? min, DateTime? max ) { return min > max; } }}

  日期范围查询条件DateSegmentCriteria,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;namespace Util.Datas.Queries.Criterias {    ///     /// 日期段过滤条件 - 不包含时间    ///     /// 
实体类型
///
属性类型
public class DateSegmentCriteria
: SegmentCriteria
where TEntity : class,IAggregateRoot { ///
/// 初始化日期段过滤条件 /// ///
属性表达式 ///
最小值 ///
最大值 public DateSegmentCriteria( Expression
> propertyExpression, DateTime? min, DateTime? max ) : base( propertyExpression, min, max ) { } ///
/// 最小值是否大于最大值 /// protected override bool IsMinGreaterMax( DateTime? min, DateTime? max ) { return min > max; } ///
/// 获取最小值 /// protected override DateTime? GetMinValue() { return base.GetMinValue().SafeValue().Date; } ///
/// 获取最大值 /// protected override DateTime? GetMaxValue() { return base.GetMaxValue().SafeValue().Date.AddDays( 1 ); } ///
/// 获取最大值相关的运算符 /// protected override Operator GetMaxOperator() { return Operator.Less; } }}

  整数范围查询条件IntSegmentCriteria,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;namespace Util.Datas.Queries.Criterias {    ///     /// 整数段过滤条件    ///     /// 
实体类型
///
属性类型
public class IntSegmentCriteria
: SegmentCriteria
where TEntity : class,IAggregateRoot { ///
/// 初始化整数段过滤条件 /// ///
属性表达式 ///
最小值 ///
最大值 public IntSegmentCriteria( Expression
> propertyExpression, int? min, int? max ) : base( propertyExpression,min,max){ } ///
/// 最小值是否大于最大值 /// protected override bool IsMinGreaterMax( int? min, int? max ) { return min > max; } }}

  double范围查询条件DoubleSegmentCriteria,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;namespace Util.Datas.Queries.Criterias {    ///     /// double数值段过滤条件    ///     /// 
实体类型
///
属性类型
public class DoubleSegmentCriteria
: SegmentCriteria
where TEntity : class,IAggregateRoot { ///
/// 初始化double数值段过滤条件 /// ///
属性表达式 ///
最小值 ///
最大值 public DoubleSegmentCriteria( Expression
> propertyExpression, double? min, double? max ) : base( propertyExpression, min, max ) { } ///
/// 最小值是否大于最大值 /// protected override bool IsMinGreaterMax( double? min, double? max ) { return min > max; } }}

  decimal范围查询条件DecimalSegmentCriteria,代码如下。

using System;using System.Linq.Expressions;using Util.Domains;namespace Util.Datas.Queries.Criterias {    ///     /// decimal数值段过滤条件    ///     /// 
实体类型
///
属性类型
public class DecimalSegmentCriteria
: SegmentCriteria
where TEntity : class,IAggregateRoot { ///
/// 初始化decimal数值段过滤条件 /// ///
属性表达式 ///
最小值 ///
最大值 public DecimalSegmentCriteria( Expression
> propertyExpression, decimal? min, decimal? max ) : base( propertyExpression, min, max ) { } ///
/// 最小值是否大于最大值 /// protected override bool IsMinGreaterMax( decimal? min, decimal? max ) { return min > max; } }}

  我们现在进行日期范围查询,就比较简单了,代码如下。

queryable.Filter( new DateSegmentCriteria
( t => t.Date, BeginDate,EndDate ) );

  不过上面的代码用起来还不是太顺手,可以将范围查询扩展到IQueryable,代码如下。

///         /// 过滤整数段        ///         /// 
实体类型
///
属性类型
/// 数据源 /// 属性表达式,范例:t => t.Age /// 最小值 /// 最大值 public static IQueryable
FilterInt
( this IQueryable
source, Expression
> propertyExpression, int? min, int? max ) where T : class,IAggregateRoot { return source.Filter( new IntSegmentCriteria
( propertyExpression, min, max ) ); } ///
/// 过滤double数值段 /// ///
实体类型
///
属性类型
///
数据源 ///
属性表达式,范例:t => t.Age ///
最小值 ///
最大值 public static IQueryable
FilterDouble
( this IQueryable
source, Expression
> propertyExpression, double? min, double? max ) where T : class,IAggregateRoot { return source.Filter( new DoubleSegmentCriteria
( propertyExpression, min, max ) ); } ///
/// 过滤decimal数值段 /// ///
实体类型
///
属性类型
///
数据源 ///
属性表达式,范例:t => t.Age ///
最小值 ///
最大值 public static IQueryable
FilterDecimal
( this IQueryable
source, Expression
> propertyExpression, decimal? min, decimal? max ) where T : class,IAggregateRoot { return source.Filter( new DecimalSegmentCriteria
( propertyExpression, min, max ) ); } ///
/// 过滤日期段,不包含时间 /// ///
实体类型
///
属性类型
///
数据源 ///
属性表达式,范例:t => t.Age ///
最小值 ///
最大值 public static IQueryable
FilterDate
( this IQueryable
source, Expression
> propertyExpression, DateTime? min, DateTime? max ) where T : class,IAggregateRoot { return source.Filter( new DateSegmentCriteria
( propertyExpression, min, max ) ); } ///
/// 过滤日期时间段,包含时间 /// ///
实体类型
///
属性类型
///
数据源 ///
属性表达式,范例:t => t.Age ///
最小值 ///
最大值 public static IQueryable
FilterDateTime
( this IQueryable
source, Expression
> propertyExpression, DateTime? min, DateTime? max ) where T : class,IAggregateRoot { return source.Filter( new DateTimeSegmentCriteria
( propertyExpression, min, max ) ); }

  日期范围查询的调用代码简化为如下代码。 

queryable.FilterDate( t => t.Date, BeginDate, EndDate)

  本文介绍了如何使用查询条件对象封装范围查询,当然你可以用类似的方法将业务中的查询条件封装起来。

  规约模式还有其它用法,更强大的用法,请参考陈晴阳老兄的这篇http://www.cnblogs.com/daxnet/p/3925426.html

 

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢大家的持续关注,我的博客地址:

 

你可能感兴趣的文章
【Java小工匠聊密码学】--对称加密--DES
查看>>
调用Thread类的方法:public final String getName() 为什么得到的线程对象的名称默认是:Thread-0、Thread-1、Thread-2、...呢?...
查看>>
Shell脚本里的双冒号是什么意思
查看>>
java基础学习_GUI_如何让Netbeans的东西Eclipse能访问、GUI(图形用户接口)_day25总结...
查看>>
从零开始学 Web 之 CSS(三)链接伪类、背景、行高、盒子模型、浮动
查看>>
java多线程--信号量Semaphore的使用
查看>>
中国存储芯片进入战略关键期
查看>>
市场上的视觉图像采集卡软硬功能对比
查看>>
阿里专家与你分享:你必须了解的Java多线程技术
查看>>
张高兴的 Windows 10 IoT 开发笔记:三轴数字罗盘 HMC5883L
查看>>
rocketmq 同步双写
查看>>
细数国内无人机的江湖门派
查看>>
张高兴的 Windows 10 IoT 开发笔记:部署 ASP.NET Core 2 应用
查看>>
Waymo已经开始绘制亚特兰大地图数据,自动驾驶汽车路测地点又添新城
查看>>
ARM 和 RISC-V 公然开撕,GNOME 之父指责 ARM
查看>>
日本将推出“隐形列车”,你要去体验一番吗?
查看>>
15 篇最新 AI 论文来袭!NLP、CV...人人有份 | 本周值得读
查看>>
Maven查看依赖树
查看>>
装饰器
查看>>
第13章 用序列化保存模型
查看>>