2009-09-16 43 views
2

我正在研究一个应用程序,允许牙医捕获有关某些临床活动的信息。虽然应用程序不是高度可定制的(没有自定义工作流程或表单),但它确实提供了一些基本的定制功能;客户可以选择用自己定制的表单域来扩充它们。管理员可以创建大约六种不同的字段类型(即文本,日期,数字,下拉等)。我们在持久性方面使用实体属性值(EAV)来模拟此功能。设计面向对象和单元测试友好查询系统

该应用程序的其他主要功能之一是能够根据这些自定义字段创建自定义查询。这可以通过一个UI来完成,其中可以创建任意数量的规则(日期< =(现在-5天),文字像'444',DropDown =='ICU')。所有规则都安排在一起产生一个查询。

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

  • 单元测试单独的规则几乎是不可能的
  • 最后一点也意味着在 未来增加额外的规则类型,肯定会违反 的开闭原则。
  • 业务逻辑和持久性问题正在混合在一起。
  • 慢速运行单元测试由于需要一个真正的数据库(SQLLite无法解析T-SQL和嘲讽出来一个解析器会嘘......硬)

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

我考虑了指挥链方面的责任模式的组合:

查询类包含抽象规则类的集合(DateRule,TextRule等)。并拥有对包含未过滤数据集的DataSet类的引用。 DataSet以持久性不可知的方式建模(即没有引用或钩入数据库类型)

规则具有一个Filter()方法,它接受DataSet,对其进行适当筛选,然后将其返回给调用者。 Query类比简单地迭代每个Rule,允许每个Rule按照它认为合适的方式过滤DataSet。一旦执行完所有规则或一旦数据集被过滤到无,执行就会停止。

让我担心这种方法的一件事是在.NET中解析一个潜在的大型未过滤数据集的性能影响。对于解决这种问题,在可维护性和性能之间提供一个很好的平衡,肯定有一些尝试和真正的方法?

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

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

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

回答

1

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

更新:你也可以尝试在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); 
+0

嘻嘻 - 不妨问他“你试过javascript吗?” – 2009-09-16 03:15:50

+0

Tim, 感谢您的回复。我不认为你有任何我可以学习的示例代码(除了你提到的Dynamic LINQ示例以外)? – 2009-09-25 03:24:45

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(); 
    } 
} 

这种方式应该让你单元测试的规则很容易地实现它。

不利的一面是,这个解决方案仍然使数据库做了很多工作,这听起来像是你不想做的事情。

+0

这绝对是一个可行的解决方案,但我希望能够设计一种避免与假脱机动态SQL(单元测试性,隔离测试规则等)相关的陷阱的解决方案。 – 2009-09-25 03:26:30