查询条件(规约模式应用)

摘要:
已经为查询条件做了一些准备。本文将介绍查询条件的封装,这是规范模式的一个应用。该规范的优点在于它可以封装一堆无组织的条件判断或查询条件,以清晰的概念表达它们,并使这些谓词可重用。为了获得健壮性,我们将对查询条件进行各种判断,从而导致代码混乱。更好的方法是将范围查询逻辑封装到查询条件对象中。当您以后需要查询范围时,您可以在指尖上找到它。
查询条件(规约模式应用)

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

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

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

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

复制代码
using System;
using System.Linq.Expressions;

namespace Util.Domains.Repositories {
    /// <summary>
    /// 查询条件
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    public interface ICriteria<TEntity> where TEntity : class,IAggregateRoot {
        /// <summary>
        /// 获取谓词
        /// </summary>
        Expression<Func<TEntity, bool>> GetPredicate();
    }
}
复制代码

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

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

复制代码
        /// <summary>
        /// 过滤
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="criteria">查询条件</param>
        public static IQueryable<T> Filter<T>( this IQueryable<T> source, ICriteria<T> 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 {
    /// <summary>
    /// 查询条件
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    public abstract class CriteriaBase<TEntity> : ICriteria<TEntity> where TEntity : class, IAggregateRoot {
        /// <summary>
        /// 谓词
        /// </summary>
        protected Expression<Func<TEntity, bool>> Predicate { get; set; }

        /// <summary>
        /// 获取谓词
        /// </summary>
        public virtual Expression<Func<TEntity, bool>> GetPredicate() {
            return Predicate;
        }
    }
}
复制代码

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

复制代码
using System;
using System.Linq.Expressions;
using Util.Domains;
using Util.Lambdas;

namespace Util.Datas.Queries.Criterias {
    /// <summary>
    /// 段过滤条件
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TProperty">属性类型</typeparam>
    /// <typeparam name="TValue">值类型</typeparam>
    public abstract class SegmentCriteria<TEntity, TProperty, TValue> : CriteriaBase<TEntity>
        where TEntity : class, IAggregateRoot
        where TValue : struct {
        /// <summary>
        /// 初始化段过滤条件
        /// </summary>
        /// <param name="propertyExpression">属性表达式</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        protected SegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, TValue? min, TValue? max ) {
            Builder = new ExpressionBuilder<TEntity>();
            PropertyExpression = propertyExpression;
            Min = min;
            Max = max;
            if ( IsMinGreaterMax( min, max ) ) {
                Min = max;
                Max = min;
            }
        }

        /// <summary>
        /// 最小值是否大于最大值
        /// </summary>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        protected abstract bool IsMinGreaterMax( TValue? min, TValue? max );

        /// <summary>
        /// 属性表达式
        /// </summary>
        public Expression<Func<TEntity, TProperty>> PropertyExpression { get; set; }

        /// <summary>
        /// 表达式生成器
        /// </summary>
        private ExpressionBuilder<TEntity> Builder { get; set; }

        /// <summary>
        /// 最小值
        /// </summary>
        public TValue? Min { get; set; }

        /// <summary>
        /// 最大值
        /// </summary>
        public TValue? Max { get; set; }

        /// <summary>
        /// 获取谓词
        /// </summary>
        public override Expression<Func<TEntity, bool>> GetPredicate() {
            var first = CreateLeftExpression();
            var second = CreateRightExpression();
            return Builder.ToLambda( first.And( second ) );
        }

        /// <summary>
        /// 创建左操作数,即 t => t.Property >= Min
        /// </summary>
        private Expression CreateLeftExpression() {
            if ( Min == null )
                return null;
            return Builder.Create( PropertyExpression, Operator.GreaterEqual, GetMinValue() );
        }

        /// <summary>
        /// 获取最小值
        /// </summary>
        protected virtual TValue? GetMinValue() {
            return Min;
        }

        /// <summary>
        /// 创建右操作数,即 t => t.Property &lt;= Max
        /// </summary>
        private Expression CreateRightExpression() {
            if ( Max == null )
                return null;
            return Builder.Create( PropertyExpression, GetMaxOperator(), GetMaxValue() );
        }

        /// <summary>
        /// 获取最大值相关的运算符
        /// </summary>
        protected virtual Operator GetMaxOperator() {
            return Operator.LessEqual;
        }

        /// <summary>
        /// 获取最大值
        /// </summary>
        protected virtual TValue? GetMaxValue() {
            return Max;
        }
    }
}
复制代码

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

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

复制代码
using System;
using System.Linq.Expressions;
using Util.Domains;

namespace Util.Datas.Queries.Criterias {
    /// <summary>
    /// 日期时间段过滤条件 - 包含时间
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TProperty">属性类型</typeparam>
    public class DateTimeSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, DateTime> where TEntity : class,IAggregateRoot {
        /// <summary>
        /// 初始化日期时间段过滤条件
        /// </summary>
        /// <param name="propertyExpression">属性表达式</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public DateTimeSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, DateTime? min, DateTime? max )
            : base( propertyExpression, min, max ) {
        }

        /// <summary>
        /// 最小值是否大于最大值
        /// </summary>
        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 {
    /// <summary>
    /// 日期段过滤条件 - 不包含时间
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TProperty">属性类型</typeparam>
    public class DateSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, DateTime> where TEntity : class,IAggregateRoot {
        /// <summary>
        /// 初始化日期段过滤条件
        /// </summary>
        /// <param name="propertyExpression">属性表达式</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public DateSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, DateTime? min, DateTime? max )
            : base( propertyExpression, min, max ) {
        }

        /// <summary>
        /// 最小值是否大于最大值
        /// </summary>
        protected override bool IsMinGreaterMax( DateTime? min, DateTime? max ) {
            return min > max;
        }

        /// <summary>
        /// 获取最小值
        /// </summary>
        protected override DateTime? GetMinValue() {
            return base.GetMinValue().SafeValue().Date;
        }

        /// <summary>
        /// 获取最大值
        /// </summary>
        protected override DateTime? GetMaxValue() {
            return base.GetMaxValue().SafeValue().Date.AddDays( 1 );
        }

        /// <summary>
        /// 获取最大值相关的运算符
        /// </summary>
        protected override Operator GetMaxOperator() {
            return Operator.Less;
        }
    }
}
复制代码

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

复制代码
using System;
using System.Linq.Expressions;
using Util.Domains;

namespace Util.Datas.Queries.Criterias {
    /// <summary>
    /// 整数段过滤条件
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TProperty">属性类型</typeparam>
    public class IntSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty,int> where TEntity : class,IAggregateRoot {
        /// <summary>
        /// 初始化整数段过滤条件
        /// </summary>
        /// <param name="propertyExpression">属性表达式</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public IntSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, int? min, int? max ) 
            : base( propertyExpression,min,max){
        }

        /// <summary>
        /// 最小值是否大于最大值
        /// </summary>
        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 {
    /// <summary>
    /// double数值段过滤条件
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TProperty">属性类型</typeparam>
    public class DoubleSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, double> where TEntity : class,IAggregateRoot {
        /// <summary>
        /// 初始化double数值段过滤条件
        /// </summary>
        /// <param name="propertyExpression">属性表达式</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public DoubleSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, double? min, double? max )
            : base( propertyExpression, min, max ) {
        }

        /// <summary>
        /// 最小值是否大于最大值
        /// </summary>
        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 {
    /// <summary>
    /// decimal数值段过滤条件
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TProperty">属性类型</typeparam>
    public class DecimalSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, decimal> where TEntity : class,IAggregateRoot {
        /// <summary>
        /// 初始化decimal数值段过滤条件
        /// </summary>
        /// <param name="propertyExpression">属性表达式</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public DecimalSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, decimal? min, decimal? max )
            : base( propertyExpression, min, max ) {
        }

        /// <summary>
        /// 最小值是否大于最大值
        /// </summary>
        protected override bool IsMinGreaterMax( decimal? min, decimal? max ) {
            return min > max;
        }
    }
}
复制代码

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

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

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

复制代码
/// <summary>
        /// 过滤整数段
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <typeparam name="TProperty">属性类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="propertyExpression">属性表达式,范例:t => t.Age</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public static IQueryable<T> FilterInt<T, TProperty>( this IQueryable<T> source,
            Expression<Func<T, TProperty>> propertyExpression, int? min, int? max ) where T : class,IAggregateRoot {
            return source.Filter( new IntSegmentCriteria<T, TProperty>( propertyExpression, min, max ) );
        }

        /// <summary>
        /// 过滤double数值段
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <typeparam name="TProperty">属性类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="propertyExpression">属性表达式,范例:t => t.Age</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public static IQueryable<T> FilterDouble<T, TProperty>( this IQueryable<T> source,
            Expression<Func<T, TProperty>> propertyExpression, double? min, double? max ) where T : class,IAggregateRoot {
            return source.Filter( new DoubleSegmentCriteria<T, TProperty>( propertyExpression, min, max ) );
        }

        /// <summary>
        /// 过滤decimal数值段
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <typeparam name="TProperty">属性类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="propertyExpression">属性表达式,范例:t => t.Age</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public static IQueryable<T> FilterDecimal<T, TProperty>( this IQueryable<T> source,
            Expression<Func<T, TProperty>> propertyExpression, decimal? min, decimal? max ) where T : class,IAggregateRoot {
            return source.Filter( new DecimalSegmentCriteria<T, TProperty>( propertyExpression, min, max ) );
        }

        /// <summary>
        /// 过滤日期段,不包含时间
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <typeparam name="TProperty">属性类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="propertyExpression">属性表达式,范例:t => t.Age</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public static IQueryable<T> FilterDate<T, TProperty>( this IQueryable<T> source,
            Expression<Func<T, TProperty>> propertyExpression, DateTime? min, DateTime? max ) where T : class,IAggregateRoot {
            return source.Filter( new DateSegmentCriteria<T, TProperty>( propertyExpression, min, max ) );
        }

        /// <summary>
        /// 过滤日期时间段,包含时间
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <typeparam name="TProperty">属性类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="propertyExpression">属性表达式,范例:t => t.Age</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        public static IQueryable<T> FilterDateTime<T, TProperty>( this IQueryable<T> source,
            Expression<Func<T, TProperty>> propertyExpression, DateTime? min, DateTime? max ) where T : class,IAggregateRoot {
            return source.Filter( new DateTimeSegmentCriteria<T, TProperty>( propertyExpression, min, max ) );
        }
复制代码

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

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

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

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

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

  谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

免责声明:文章转载自《查询条件(规约模式应用)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇error while loading shared libraries: libhwloc.so.5: cannot open shared object file: No such file or directory【转】 如何利用C#代码来进行操作AD下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

xml转json和实体类的两种方式

本文为博主原创,未经允许不得转载: xml在http通信中具有较高的安全性和传输速度,所以应用比较广泛, 在项目中往往需要对xml,json和实体类进行相互转换,在这里总结一下自己所用到的一些方法: 一. 使用XMLSerializer 将xml转为json格式 1.引入jar包: <dependency> <g...

使用OQL“语言”构造ORM实体类的复杂查询条件

OQL”语言“ 是PDF.NET数据开发框架的实体对象查询语言,一直以来,ORM的复杂查询条件都是困扰ORM的问题,所以很多时候不得不舍弃ORM,直接手工拼接SQL。我们来看看OQL是怎么解决这些问题的,现在举一个今天同事遇到的问题: 有这样一个实体类 MyEntity,实体类的具体定义在此忽略,有兴趣的朋友请看我的博客。 MyEntity e=new ...

hibernate自动建表

x'ml配置文件 <!-- 扫描所有实体类 --> <property name="packagesToScan"> <list> <value>com.fit.core.pojo</value>...

asp.net core系列 31 EF管理数据库架构--必备知识 反向工程

一.   反向工程   反向工程是基于数据库架构,生成的实体类和DbContext类代码的过程,对于Visual Studio开发,建议使用PMC。对于其他开发环境,请选择.NET Core CLI工具(跨平台)。     (1) 在程序包管理器控制台(PMC)工具中使用命令Scaffold-DbContext 来进行反向工程。     (2) 在.NET...

Swagger学习笔记

查看swagger教学视频,请点击 《狂神说java》: https://www.bilibili.com/video/BV1Y441197Lw?p=1 记得投币三连呀~~ Swagger 学习目标: 了解Swagger的作用和概念 了解前后端分离 在SpringBoot中继承Swagger Swagger简介 前后端分离 Vue + SpringBo...

mybatis多表查询

Mybatis实现多表查询有三种方式: (需求:给一个实体类设置属性值,但该实体类的数据保存在数据库的两张表中) 1,分别对两张表进行查询,将获取到的数据分别赋值给实体类。 2,编写多表查询的sql语句,通过给查询到的数据设置与实体类相同的别名,使用Auto Mapping特性,将查询结果自动映射到实体类。 3,使用MyBatis的<resultMa...