设计OO和单元测试友好查询系统

时间:2009-09-16 02:29:02

标签: c# unit-testing design-patterns complexity-theory

我正在开发一个应用程序,允许牙医捕获有关某些临床活动的信息。虽然应用程序不是高度可定制的(没有自定义工作流程或表单),但它确实提供了一些基本的自定义功能;客户可以选择使用自己的自定义表单字段来扩充预定义的表单字段。管理员可以创建大约六种不同的字段类型(即Text,Date,Numeric,DropDown等)。我们在持久性方面使用实体 - 属性 - 值(EAV)来模拟此功能。

该应用程序的其他一些关键功能是能够针对这些自定义字段创建自定义查询。这是通过UI完成的,其中可以创建任意数量的规则(Date< =(现在 - 5天),Text Like'444',DropDown =='ICU')。所有规则都是AND,以产生查询。

当前的实现(我“继承”)既不面向对象也不面向单元测试。本质上,有一个单独的“上帝”类将所有无数的规则类型直接编译成复杂的动态SQL语句(即内连接,外连接和子选择)。由于以下几个原因,这种方法很麻烦:

  • 单独测试单个规则 几乎不可能
  • 最后一点也意味着在其中添加其他规则类型 未来绝对会违反 开放封闭原则。
  • 正在混合业务逻辑和持久性问题。
  • 运行单元测试速度慢,因为需要一个真正的数据库(SQLLite无法解析T-SQL并且嘲笑解析器会很难......)

我正在努力想出一个灵活,可维护和可测试的替代设计,同时仍然保持查询性能相当活泼。最后一点是关键,因为我认为基于OOAD的实现将至少将一些数据过滤逻辑从数据库服务器移动到(.NET)应用程序服务器。

我正在考虑将Command和责任链模式结合起来:

Query类包含一组抽象规则类(DateRule,TextRule等)。并保存对包含未经过滤的数据集的DataSet类的引用。 DataSet以持久性不可知的方式建模(即没有引用或挂钩到数据库类型中)

Rule有一个Filter()方法,它接受一个DataSet,适当地过滤它,然后将它返回给调用者。 Query类比简单地迭代每个规则,允许每个规则在其认为合适时过滤DataSet。一旦执行了所有规则或者将DataSet过滤为空,就会停止执行。

让我担心这种方法的一件事是解析.NET中潜在的大型未过滤数据集的性能影响。当然,有一些经过验证的方法可以解决这种在可维护性和性能之间取得良好平衡的问题吗?

最后一点:管理层不允许使用NHibernate。 Linq to SQL可能是可能的,但我不确定该技术对于手头的任务有多适用。

非常感谢,我期待着大家的反馈!

更新:仍在寻找解决方案。

2 个答案:

答案 0 :(得分:1)

我认为LINQ to SQL可能是一个理想的解决方案,可能与VS2008样本中的Dynamic LINQ耦合。使用LINQ,尤其是IEnumerable / IQueryable上的扩展方法,您可以使用标准和自定义逻辑构建查询,具体取决于您获得的输入。我大量使用这种技术来实现我的许多MVC操作的过滤器,效果很好。因为它实际上构建了一个表达式树,然后使用它在需要实现查询的位置生成SQL,我认为它对于您的场景是理想的,因为大部分繁重的工作仍由SQL服务器完成。在LINQ证明生成非最佳查询的情况下,您始终可以使用添加到LINQ数据上下文中的表值函数或存储过程作为利用优化查询的方法。

已更新:您也可以尝试在Nutshell中使用C#3.0中的PredicateBuilder

示例:查找标题包含一组搜索词之一且出版商为O'Reilly的所有图书。

 var predicate = PredicateBuilder.True<Book>();
 predicate = predicate.And( b => b.Publisher == "O'Reilly" );
 var titlePredicate = PredicateBuilder.False<Book>();
 foreach (var term in searchTerms)
 {
     titlePredicate = titlePredicate.Or( b => b.Title.Contains( term ) );
 }
 predicate = predicate.And( titlePredicate );

 var books = dc.Book.Where( predicate );

答案 1 :(得分:0)

我看到它完成的方式是创建对象,为用户构建查询的每个条件建模,并使用这些对象构建对象树。

从对象树中,您应该能够递归地构建满足查询的SQL语句。

你需要的基本元素是AND和OR对象,以及模型比较的对象,比如EQUALS,LESSTHAN等。你可能想要使用这些对象的接口来将它们链接在一起方式更容易。

一个简单的例子:

public interface IQueryItem
{
    public String GenerateSQL();
}


public class AndQueryItem : IQueryItem
{
    private IQueryItem _FirstItem;
    private IQueryItem _SecondItem;

    // Properties and the like

    public String GenerateSQL()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(_FirstItem.GenerateSQL());
        builder.Append(" AND ");
        builder.Append(_SecondItem.GenerateSQL());

        return builder.ToString();
    }
}

以这种方式实现它应该允许您非常容易地对规则进行单元测试。

从消极方面来说,这个解决方案仍然会让数据库做很多工作,听起来你真的不想这样做。