SimpleCondition与ConditionSet
查询是最常用的数据库操作,最普通的查询条件一般是某个字段满足某个条件,这类查询可能超过了一半。
因此MyOrm里定义了SimpleCondition和ConditionSet两个Condition类型。SimpleCondition的定义非常简单:名称、用于比较的操作符、值。为操作符定义了ConditionOperator的枚举,定义为:
- /// <summary>
- /// 条件判断操作符
- /// </summary>
- public enum ConditionOperator
- {
- /// <summary>
- /// 相等
- /// </summary>
- Equals = 0,
- /// <summary>
- /// 大于
- /// </summary>
- LargerThan = 1,
- /// <summary>
- /// 小于
- /// </summary>
- SmallerThan = 2,
- /// <summary>
- /// 以指定字符串为开始(作为字符串比较)
- /// </summary>
- StartsWith = 3,
- /// <summary>
- /// 以指定字符串为结尾(作为字符串比较)
- /// </summary>
- EndsWith = 4,
- /// <summary>
- /// 包含制定字符串(作为字符串比较)
- /// </summary>
- Contains = 5,
- /// <summary>
- /// 逻辑否(非判断操作符)
- /// </summary>
- Not = 0xF000,
- /// <summary>
- /// 不相等
- /// </summary>
- NotEquals = Not | Equals,
- /// <summary>
- /// 小于或等于
- /// </summary>
- NotLargerThan = Not | LargerThan,
- /// <summary>
- /// 大于或等于
- /// </summary>
- NotSmallerThan = Not | SmallerThan,
- /// <summary>
- /// 以指定字符串为开始(作为字符串比较)
- /// </summary>
- NotStartsWith = Not | StartsWith,
- /// <summary>
- /// 以指定字符串为结尾(作为字符串比较)
- /// </summary>
- NotEndsWith = Not | EndsWith,
- /// <summary>
- /// 不包含制定字符串(作为字符串比较)
- /// </summary>
- NotContains = Not | Contains
- }
基本上包括了常用的比较符,定义的比较繁琐,是因为SQL语句里对于Not的表达方式是多种的,为了拼SQL方便。把SimpleCondition解析为SQL,是在ObjectDAOBase的BuildSimpleConditionSql(SimpleCondition simpleCondition, IList outputParams)方法里实现。
ConditionSet是Condition的集合,可以看作是多个Condition的树状结构,另外定义了ConditionJoinType表示通过And或Or连接。
Condition的定义是和数据库无关的,它是一般查询的通用的表达方式。如果有可能,其他类型的查询也可以通过Condition来表示。其实LINQ已经实现了统一的方式,它考虑的更严谨更复杂,但是也并不能完全满足SQL查询的要求。
Search方法
然后就是查询的方法了。Search方法的定义很简单:
- public virtual List<T> Search(Condition condition)
- {
- using (IDbCommand command = MakeConditionCommand("select @AllFields from @FromTable where @Condition", condition))
- {
- return ReadAll(command.ExecuteReader());
- }
- }
其中使用了MakeConditionCommand,在MakeConditionCommand的SQLWithParam参数里用"@AllFields","@Table","@FromTable","@Condition"标记分别代表SQL语句里的所有列、表名、多表连接和where条件,而where条件是通过condition参数生成的,至于怎么生成在BuildConditionSql(Condition conditon, IList outputParams)里实现。
了解了MakeConditionCommand之后也可以通过它方便的构造Command,例如
- MakeConditionCommand("select count(*) from @FromTable ",null)
得到的是求所有行数的Command;
- MakeConditionCommand("delete from @Table where @Condition", condition)
得到的是删除所有满足条件的数据的Command。
Search的使用
使用也很简单,只需要生成Condition就可以了。
例如需要查询所有CategoryName为Seafood,ProductName中包含"Coffee",UnitPrice大于2.5的ProductsView:
- ConditionSet conditions = new ConditionSet();
- conditions.Add(new SimpleCondition("Category_CategoryName", "Seafood"));
- conditions.Add(new SimpleCondition("ProductName", ConditionOperator.Contains, "Coffee"));
- conditions.Add(new SimpleCondition("UnitPrice", ConditionOperator.LargerThan, 2.5));
- List<ProductsView> products = new ProductsViewDAO().Search(conditions);
如果ProductName的条件改为:包含"Coffee"或者为空,那么改成这样:
- ConditionSet conditions = new ConditionSet();
- conditions.Add(new SimpleCondition("Category_CategoryName", "Seafood"));
- ConditionSet subConditions = new ConditionSet(ConditionJoinType.Or);
- subConditions.Add(new SimpleCondition("ProductName", ConditionOperator.Contains, "Coffee"));
- subConditions.Add(new SimpleCondition("ProductName", null));
- conditions.Add(subConditions);
- conditions.Add(new SimpleCondition("UnitPrice", ConditionOperator.LargerThan, 2.5));
- List<ProductsView> products = new ProductsViewDAO().Search(conditions);
稍微繁琐了些,不过比拼字符串的方式要让人放心一些。LINQ之所以能那么简洁是因为编译器的帮助,这方面是比不了了。
ProductsDAO和ProductsViewDAO的自定义方法
虽然Search方法可以完成一般的查询,但是免不了有无法完成的查询。建议在继承ObjectDAO<T>和ObjectViewDAO<T>的实体类DAO中通过自定义方法封装查询。例如需要查询订单总额在指定金额以上的ProductsView,定义GetAllWithTotalTurnoverLargerThan方法:
- public List<ProductsView> GetAllWithTotalTurnoverLargerThan(float totalTurnover)
- {
- using (IDbCommand command = MakeParamCommand(String.Format("select {0} from {1} where (select sum({2} * {3}) from {4} where {4}.{5} = {6}.{7}) > {8}",
- AllFieldsSql,
- FromTable,
- ToSqlName(OrderDetails._UnitPrice),
- ToSqlName(OrderDetails._Quantity),
- ToSqlName(Configuration.TableInfoProvider.GetTableInfo(typeof(OrderDetails)).TableName),
- ToSqlName(OrderDetails._ProductID),
- ToSqlName(TableName),
- ToSqlName(Products._ProductID),
- ToSqlParam("0")),
- new object[] { totalTurnover }))
- {
- return ReadAll(command.ExecuteReader());
- }
- }
其中OrderDetails._UnitPrice、OrderDetails._Quantity是表示相应属性名的字符串常量。也可以直接用字符串,但是属性名不正确的话在运行时才能发现。
另外还有常用的包含业务逻辑的查询,虽然也是简单的查询,但是最好还是封装为相应的方法。ProductsDAO和ProductsViewDAO的实际定义:
- public class ProductsDAO : ObjectDAO<Products>, IProductsDAO
- {
- public Products GetProductOfOrderDetail(OrderDetails orderDetails)
- {
- return GetObject(orderDetails.ProductID);
- }
- public List<Products> GetAllWithCategory(Categories category)
- {
- return Search(new SimpleCondition(Products._CategoryID, category.CategoryID));
- }
- public List<Products> GetAllWithSupplier(Suppliers supplier)
- {
- return Search(new SimpleCondition(Products._SupplierID, supplier.SupplierID));
- }
- }
- public class ProductsViewDAO : ObjectViewDAO<ProductsView>, IProductsViewDAO
- {
- public ProductsView GetProductOfOrderDetail(OrderDetails orderDetails)
- {
- return GetObject(orderDetails.ProductID);
- }
- public List<ProductsView> GetAllWithCategory(Categories category)
- {
- return Search(new SimpleCondition(ProductsView._CategoryID, category.CategoryID));
- }
- public List<ProductsView> GetAllWithSupplier(Suppliers supplier)
- {
- return Search(new SimpleCondition(ProductsView._SupplierID, supplier.SupplierID));
- }
- }
这样的封装可以满足对于权限、日志等等的要求,因为它反映的是实际业务的需求,而不是纯粹的查询。
接下来会介绍一些MyORM结合界面使用的例子。