65.9K
CodeProject 正在变化。 阅读更多。
Home

另一个过滤器库

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2009年5月1日

CPOL

2分钟阅读

viewsIcon

25776

downloadIcon

367

一个帮助在 SQL 语句中创建 WHERE 子句或为 System.Data.DataRow 选择操作创建过滤器表达式的类库。

Filters_src

引言

有时当我们学习一项新技术或一门新编程语言时,我们试图找到一种方法来做我们已经熟悉的技术所做的事情。 这就是五年前(或更早)我读到关于 C# 运算符重载时发生的事情。 运算符重载对于 VB 程序员来说不是常见的话题,并且重载运算符来处理复数并不在我的工作范围内。 在本文中,我想向您展示一小组类,这些类可以通过简单地组合表达式和逻辑运算符“&”、“|”和“!”来生成强大的WHERE条件在 SQL 查询语句中。

问题所在

想象一下您有一个如上所示的用户界面,用户可以选择一个或多个搜索选项,然后您编写一个 SQL 语句来检索匹配的记录。

如果您通过组合 UI 上的值和数据库表上的字段来编写语句,则应小心添加正确的条件,平衡括号,并将参数添加到命令中。

解决方案:运算符重载

我定义了一个基类Expression,它代表WHERE子句中的一个通用术语。 然后,我派生了两个类:FieldExpression,它代表一个字段条件,以及LiteralExpression,它代表一个基于通用值的条件。 Expression基类实现了Expression比较所需的所有运算符。

public abstract class Expression
{
    public static Predicate operator ==(Expression expr1, Expression expr2)
    {
        return new Predicate(expr1, expr2, CompareOperator.Equal);
    }
    public static Predicate operator !=(Expression expr1, Expression expr2)
    {
        return new Predicate(expr1, expr2, CompareOperator.NotEqual);
    }

    public static Predicate operator >=(Expression expr1, Expression expr2)
    {
        return new Predicate(expr1, expr2, CompareOperator.GreaterOrEqual);
    }

    public static Predicate operator <=(Expression expr1, Expression expr2)
    {
        return new Predicate(expr1, expr2, CompareOperator.LessOrEqual);
    }
}

两个Expression之间的每次操作都会产生一个Predicate。 Predicate 可以使用逻辑运算符“&”、“|”和“!”与其他Predicate组合,并且每个组合都可以用括号括起来,以便获得不同的评估优先级。 每个Predicate都是一棵树,其中节点是一组作为 AND 或 OR 子句处理的条件,叶子是要评估的Expression。 predicate 类能够递归地遍历其自身的层次结构并呈现基本的WHERE语句。 当到达叶节点(即,属性类型为NodeKind.ExpressionPredicate)时,它使用ExpressionRenderDelegate调用回调函数。

private void Render(ExpressionRenderDelegate target, 
                    StringBuilder filter, int nestedlevel)
{
    if (this.kind == NodeKind.Expression)
    {
        target(filter, this.left, this.right, this.compare);
    }
    else
    {
        string comma = "";
        if (this.kind == NodeKind.And)
        {
            foreach (Predicate p in this.siblings)
            {
                filter.Append(comma);
                p.Render(target, filter, nestedlevel + 1);
                comma = " and ";
            }
        }
        if (this.kind == NodeKind.Or)
        {
            filter.Append("(");
            foreach (Predicate p in this.siblings)
            {
                filter.Append(comma);
                p.Render(target, filter, nestedlevel + 1);
                comma = " or ";
            }
            filter.Append(")");
        }
    }
}

我使用了这种策略进行语句渲染,因为我想让Predicate类相当抽象,并拥有用于生成数据库特定WHERE子句的专用类(例如,请参阅两种实现:DataRowFilterRenderSqlFilterRender)。 仅此而已。

演示项目

您可以使用包含的 FiltersTest 应用程序测试 Filter 库。 它需要连接到著名的 Northwind SQL Server 数据库,因此请修改 *FiltersTest.exe.config* 文件中的连接字符串以适应您的数据库服务器。 然后,如果您进入 *Form1.cs*,您可以看到一个如何使用库在下面显示的button1_Click中的示例。

private void button1_Click(object sender, EventArgs e)
{
    Predicate p, p1 = null, p2 = null, p3 = null;
    if(cmbcust.SelectedIndex >= 0) {
        p1 = (FieldExpression)"CustomerID" == 
             (LiteralExpression<string>)cmbcust.SelectedValue.ToString();
    }
    if (cmbemp.SelectedIndex >= 0)
    {
        p2 = (FieldExpression)"EmployeeID" == 
             (LiteralExpression<int>)(int)cmbemp.SelectedValue;
    }
    if (cmbship.SelectedIndex >= 0)
    {
        p3 = (FieldExpression)"ShipVia" == 
             (LiteralExpression<int>)(int)cmbship.SelectedValue;
    }
    if (optOr.Checked)
    {
        p = p1 | p2 | p3;
    }
    else
    {
        p = p1 & p2 & p3;
    }
    if (optExclude.Checked)
    {
        p = !p;
    }

    SqlFilterRender f = new SqlFilterRender("select * from Orders");
    textBox1.Text = f.Render(p);
    f.Command.Connection = this.ordersTableAdapter.Connection;
    SqlDataAdapter da = new SqlDataAdapter(f.Command);
    this.northwindDataSet.Orders.Clear();
    da.Fill(this.northwindDataSet.Orders);

}

未来工作

Filter 库不完整,SQL 语言中还有很多运算符尚未实现(inbetweenis null 等),并且并非所有基本数据类型都已处理。 我正在努力,但与此同时,请随时添加您的贡献...

© . All rights reserved.