一种通用查询语言的定义与实践

最近发现在项目中或许会遇到让用户自己构建查询表达式的情况。比如需要通过一种可配置的界面,来让用户输入一组具有逻辑关系的查询表达式,然后根据这个查询表达式来过滤并返回所需要的数据。这种用户案例其实非常常见。由此受到启发,或许我们可以自己定义一种通用的面向查询的领域特定语言(DSL),来实现查询的序列化和动态构建。

概述

由此我发布了一个称为Unified Queries(以下简称UQ)的开源项目,UQ定义了一种DSL,用以描述一种查询的特定结构。它同时还提供了将查询规约(Query Specification)转换为SQL WHERE子句以及Lambda表达式的功能。UQ提供了非常灵活的框架设计,能够非常方便地通过实现IQuerySpecificationCompiler接口,或者继承QuerySpecificationCompiler<T>抽象类来自定义查询规约的转换功能。

DSL结构定义

下面的XSD架构(XSD Schema)定义了UQ的DSL语义,需要注意的是,它包含了一组递归的层次结构:

例子

假定在QuerySpecificationSample.xml文件中定义了如下的查询规约,在执行该查询规约时,系统将返回所有名字以“Peter”开头,并且姓氏中不含有“r”字符,以及年收入在30000以上的客户。

<?xml version="1.0" encoding="utf-8"?>
<QuerySpecification>
  <LogicalOperation Operator="And">
    <Expression Name="FirstName" Type="String" Operator="StartsWith" Value="Peter"/>
    <UnaryLogicalOperation Operator="Not">
      <LogicalOperation Operator="Or">
        <Expression Name="LastName" Type="String" Operator="Contains" Value="r"/>
        <Expression Name="YearlyIncome" Type="Decimal" Operator="LessThanOrEqualTo" Value="30000"/>
      </LogicalOperation>
    </UnaryLogicalOperation>
  </LogicalOperation>
</QuerySpecification>

以下C#代码将根据该xml文件产生SQL的WHERE子句:

static void Main(string[] args)
{
    var querySpecification = QuerySpecification.LoadFromFile("QuerySpecificationSample.xml");
    var compiler = new SqlWhereClauseCompiler();
    Console.WriteLine(compiler.Compile(querySpecification));
}

所产生的SQL WHERE子句如下:

((FirstName LIKE ‘Peter%‘) AND (NOT ((LastName LIKE ‘%r%‘) OR (YearlyIncome <= 30000))))

然而在很多情况下,ADO.NET的开发人员更喜欢通过使用DbParameter来指定查询中所包含的参数值,而不是简单地将参数拼接在SQL语句中。UQ通样能够产生带有参数列表的SQL WHERE子句。要达到这样的效果,仅需在初始化SqlWhereClauseCompiler时,将构造函数参数设置为true即可:

var compiler = new SqlWhereClauseCompiler(true);

于是产生的SQL WHERE子句就是:

((FirstName LIKE @fvP8gN) AND (NOT ((LastName LIKE @ESzoyd) OR (YearlyIncome <= @fG5Z7e))))

参数值则可以通过SqlWhereClauseCompiler的ParameterValues属性获得。

事实上SqlWhereClauseCompiler所产生的SQL WHERE子句是满足Microsoft SQL Server需要的,如果您希望能够产生符合Oracle或MySQL语法的WHERE子句,可以自己扩展SqlWhereClauseCompiler类来实现。

接下来,下面的C#代码可以将上面的xml文件中所定义的查询规约编译成Lambda表达式:

static void Main(string[] args)
{
    var querySpecification = QuerySpecification.LoadFromFile("QuerySpecificationSample.xml");
    var compiler = new LambdaExpressionCompiler<Customer>();
    Console.WriteLine(compiler.Compile(querySpecification));
}

产生的Lambda表达式如下:

p => (p.FirstName.StartsWith("Peter") AndAlso Not((p.LastName.Contains("r") OrElse (p.YearlyIncome <= 30000))))

下面的C#例子详细描述了如何在一组客户对象上应用查询规约,并将满足条件的客户数据返回:

private static Customer[] GetAllCustomers()
{
    return new[]
               {
                   new Customer { FirstName = "Sunny", LastName = "Chen", YearlyIncome = 10000 },
                   new Customer { FirstName = "PeterJam", LastName = "Yo", YearlyIncome = 10000 },
                   new Customer { FirstName = "PeterR", LastName = "Ko", YearlyIncome = 50000 },
                   new Customer { FirstName = "FPeter", LastName = "Law", YearlyIncome = 70000 },
                   new Customer { FirstName = "Jim", LastName = "Peter", YearlyIncome = 30000 }
               };
}

static void Main(string[] args)
{
    var querySpecification = QuerySpecification.LoadFromFile("QuerySpecificationSample.xml");
    var compiler = new LambdaExpressionCompiler<Customer>();
    var customers = GetAllCustomers();
    foreach (var customer in customers.Where(compiler.Compile(querySpecification).Compile()))
    {
        Console.WriteLine(
            "FirstName: {0}, LastName: {1}, YearlyIncome: {2}",
            customer.FirstName,
            customer.LastName,
            customer.YearlyIncome);
    }
}

总结

现在我们已经有了一种查询结构的DSL定义,这就使得一个查询规约可以保存在内存的对象中,也可以被持久化到外部的存储系统,比如xml文件中,或者数据库中。接下来我们可以设计一种通用的界面,通过这个界面来设计一个查询规约,于是,就可以通过Compiler将所设计的查询规约转换为另一种可被已有系统接受的形式。更进一步,我们还可以设计一系列的Builder,将SQL WHERE子句或者Lambda表达式转换为UQ中的查询规约。

希望这个小项目能够给大家带来启发和帮助。

一种通用查询语言的定义与实践,布布扣,bubuko.com

时间: 2024-07-29 11:15:00

一种通用查询语言的定义与实践的相关文章

一种通用的树形二进制协议描述方法与处理算法

概述: 本方法定义了一种数据结构,可用于描述任意的树形二进制协议,并配合一个特定的处理算法,可实现一种通用的,由该种树形二进制协议定义的比特流解析与填充的处理,该数据结构的定义如下: /* 以下结构用于定义一个协议节点的描述信息. */ struct _proto_info; typedef struct _proto_des { const char *              name; /* 用于描述一个协议节点的名称. */ size_t                    stat

ASIC设计中一种通用型并行设计方法

我是个"低调"的人,总不喜欢表达出来,对异性如此,对工作也是如此.在翔哥的鼓励下,决定把自己工作的一些经验和思考写下来,和同道们一起分享. ASIC设计中一种通用型并行设计方法: 1)流水网的概念提出 IC设计中的控制有串行和并行两种思想.状态机方法反应了串行控制思想,有软件的思路,比较好理解,新手比较喜欢用.流水线方法是并行处理的思想,比较抽象,因为其效率高,老手喜欢用.从"流水线"的名字就容易知道,它只是一维的一条线,一个设计中可以有很多条.小设计中可能很容易设

一种通用的网页相似度检测算法

如果我们需要在海量的结构未知的网页库中找到和指定的网页相似度比较高的一些网页,我们该怎么办呢?本文提出的"一种通用的网页相似度检测算法"就是专门解决这个问题. 算法如下: 1.提取网页文本.这个提取步骤不要求精确,也没办法精确,因为你面对的是未知结构的网页,所以只需要提取去掉标签之后的文本即可. 2.对提取的文本进行分词.我们使用开源的中文分词组件word分词. 3.为每一个网页建立一个词向量,向量的维度就是两个网页的不重复词的并集,每一个维度的权重就是词频TF,我们这里忽略IDF也不

ASP.NET用户控件事件的定义和实践

假定用户控件(UserControl.ascx)中包含按钮控件  AButton,希望实现按  Button  按钮时,包含该用户控件的页面可以接收到事件. UserControl.ascx.cs  中的处理: 1. 定义  public  的事件委托,如  ClickEventHandler; 2. 在  UserControl  类中声明事件,如  Click; 3. 在  UserControl  类中定义引发事件的方法,如  OnClick()方法; 4. 在  UserControl 

一种通用数据采集的schema定义形式

{ "name": "凤凰金融", "notice": { "data": "attribute", "matcher": [ { "match": "xpath", "pattern": "//*[@id=\"page-financing\"]/div[1]/div[5]/div/div/div[

SQL SERVER几种数据迁移/导出导入的实践

SQLServer提供了多种数据导出导入的工具和方法,在此,分享我实践的经验(只涉及数据库与Excel.数据库与文本文件.数据库与数据库之间的导出导入). (一)数据库与Excel 方法1: 使用数据库客户端(SSMS)的界面工具.右键选择要导出数据的数据库,选择“任务”——“导出数据”,下图1,按照向导一步一步操作即可.而导入则相反,导入时,SQLServer会默认创建一张新表,字段名也默认跟导入的Excel标题一样,并且会默认字段数据类型等.当然在可以在向导进行修改.需要注意的是如果标题不是

css样式优化 设置一种通用的class类

这里,我说的是“通常情况下”,因为其他测试引擎可能对容器和测试有截然不同的定义.但一般情况下,测试就是单个的方法,容器指的就是测试类. 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的影响,单单是自己的念想受到了一定得局限,想法不能够像平地而起的高楼大厦建成一样.可是那大楼也是有烂尾的呀,我觉得最重要的还是外在环境与个人观念的先决条件,决定了拖延症的症状的好坏,有那么一些人,它也有拖延症,但是它在拖的中间,想的更多,看的更远.事情在做的时候

递归转换为迭代的一种通用方式

把递归算法转化为非递归算法, 有如下两种基本方法:1)通过分析, 用迭代的方式自底向上. 有时需用栈保存参数2)模拟函数调用过程, 用栈保存入参 尾递归: 一个函数只在return处调用自身.很多编译器就能将其转换为迭代 T tailRecursive(U x, V y) { if (bar(x, y)) { return exit(x,y); } else { ... // block 1 return tailRecursiveFoo(w, z); } } To: T Iterative(U

简单介绍 java 中的几种 &quot;通用方法“

前言 Java中,除了基本的数值类型,其他所有数据类型(包括数组)都是对象. 而Object这个类是所有类的超类,它提供的方法,自然能够使用于它的所有子类(所有非基本数值类型). 本文介绍了Object类的几种经典方法,还算比较常用. Class getClass(),ClassSuperclass () 返回包含对象信息的类对象.此方法将在以后讲反射机制的时候详细讲解. Object clone () 拷贝方法.此方法将在以后专门开篇讲解. int hashCode () 返回对象的哈希值.