zoukankan      html  css  js  c++  java
  • 一种通用查询语言的定义与实践

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

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

  • 相关阅读:
    poj3268(Silver Cow Party)最短路
    关于Phaser
    关于StampedLock
    关于AQS
    关于Exechanger
    关于Semaphore
    关于CyclicBarrier
    关于CountDownLatch
    关于BlockingQueue
    关于ThreandLocal
  • 原文地址:https://www.cnblogs.com/daxnet/p/3925426.html
Copyright © 2011-2022 走看看