NewLife.XCode是一个有15年历史的开源数据中间件,支持netcore/net45/net40,由新生命团队(2002~2020)开发完成并维护至今,以下简称XCode。
整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中,代表作有百亿级大数据实时计算项目。
开源地址:https://github.com/NewLifeX/X (求star, 1077+)
常用数据库操作添删改查,但实际上80%以上场景可能都是查询。XCode内置准备了众多数据查询方法,在泛型实体基类中提供,所有实体类可以直接使用。
查找单行数据
单行查找的本质,就是查找满足条件的第一行数据!
单行查找方法一般叫Find,(取名来自List<T>),返回一个实体对象,根据某条件查找也就是 FindByCode/FindByParentIdAndName 等。
内置单行查找方法:
- Find(Expression where)。标准用法,构建查询表达式。例 Find(_.Code=="1234abcd")
- Find(String whereClause)。旧版用法,不推荐。例 Find("Code=1234abcd")
- Find(String name, Object value)。清爽用法。例 Find(__.Code, "1234abcd")
- Find(String[] names, Object[] values)。臃肿用法,不推荐。例 Find(new []{"ParendId", "Name"}, new []{1, "role"})
- FindByKey(Object key)。主键查找,不推荐。例 FindByKey("1234abcd")
- FindByKeyForEdit(Object key)。为编辑而查找,魔方专用。
- FindMin(String field, Expression where = null)。查找指定字段的最小值
- FindMax(String field, Expression where = null)。查找指定字段的最大值
自2006年以来,经过14年发展表明,最有价值的是 Find(Expression where)
。其它方法将会逐步淘汰。
注意:
下划线“_”是每个实体类都拥有的内嵌类,它用于快速访问该实体类的每个字段元数据,例如 _.Code 。既然是内嵌类,那么就需要在实体类内部才能直接使用,所以一般建议在Biz业务类内部写各种自定义的查询方法,代码可以很精简。如果是在实体类外部使用,就得假设实体类类名了,例如 User._.Code。如果外部使用,还可以使用 using static XCode.Membership.User;,然后该外部代码也可以直接使用 _.Code。
查找多行数据
多行查询是XCode最重要的功能,没有之一!
多行查找一般叫FindAll,(取名来自List<T>),返回一个实体列表 IList<TEntity>,即使没有数据也返回空列表,该约定确保使用者可以不必判空。根据某些条件查找也就是 FindAllByRoleId 等。
多年经验,最强最好用的查询方法:
IList<TEntity> FindAll(Expression where, PageParameter page = null, String selects = null)
- where查询条件,借助WhereExpression构建复杂表达式
- page 分页与排序。
- 若RetrieveTotalCount=true,内部除了查数据,还执行一次FindCount把满足条件的总数返回TotalCount
- 若RetrieveState=true,内部还将根据 Meta.Factory.SelectStat 执行一次聚合统计,结果返回State,一般用于数据求和求平均等。
- selects 指定要查询的字段,默认null查询所有字段
例子:
var list1 = User.FindAll(_.Code.StartsWith("021.") & _.Enable==true, null, null); var list2 = User.FindAll(_.RoleId==1 & _.CreateTime>DateTime.ToDay, new PageParameter{PageSize=100});
过去很长一段时间里,用得最多的5参数版,同时也是所有FindAll的最底层:
IList<TEntity> FindAll(String where, String order, String selects, Int64 startRowIndex, Int64 maximumRows)
最经典的批量查询,看这个 Select @selects From Table Where @where Order By @order Limit @startRowIndex,@maximumRows
,你就明白各参数的意思了。内部就是为了构造这么一个语句,所以,继续在where里面写groupby和having也是可以的。
内置的其它多行查询方法:
- FindAll() 查全表,数据量大于1000时慎用
- FindAll(String sql) 直接写sql,大招
- FindAll(Expression where, String order, String selects, Int64 startRowIndex, Int64 maximumRows) 过渡版,很少用
- DbTable FindData(Expression where, String order, String selects, Int64 startRowIndex, Int64 maximumRows) 只是查出来数据集,并没有填充到实体类。DbTable可以轻易保存为二进制文件并恢复。
- FindAllWithCache() 实体缓存里的全部数据,它会把整个数据表缓存到内存,不建议在大于1000行的表上使用。
查找记录数(分页基础)
查找记录数一般叫FindCount。早期为了适配 ObjectDataSource ,FindCount与FindAll配套出现,并且参数名一致。实际上,对于FindCount来说,分页和排序参数是没有任何意义的。
- FindCount(Expression where, String order = null, String selects = null, Int64 startRowIndex = 0, Int64 maximumRows = 0) 标准用法
- FindCount(String where, String order = null, String selects = null, Int64 startRowIndex = 0, Int64 maximumRows = 0) 传统用法
在以前,FindCount 主要用于查记录数作为分页或者判断数据是否存在的依据;
在今天,FindCount 已经几乎没有什么使用价值,判断数据是否存在时,直接FindAll把数据查出来更简单,尽管稍微有点浪费,但XCode的超高性能不就是用来给开发者浪费的吗?
在ASP.Net MVC和asp.net core 下,查记录数一般在 FindAll 时设置 page.RetrieveTotalCount=true,两个事情一块做了。
获取查询SQL
XCode不支持多表关联Join,实在需要,可以用子查询替代。而复杂的子查询,就需要借助FindSQL了
SelectBuilder FindSQL(String where, String order, String selects, Int32 startRowIndex = 0, Int32 maximumRows = 0) SelectBuilder FindSQLWithKey(String where = null)
FindSQL操作并不会执行数据库查询操作,仅仅是构建一个SQL子句。
例如,查找幼儿园中班的所有班男生:
var list = Student.FindAll(_.ClassId.In(Class.FindSQLWithKey(Class._.Type=="中班")) & _.Sex==SexKinds.男);
查询生成的语句:
select * from Student where ClassId in (select id from Class where type='中班') and sex=1
注意,强烈建议Student.Biz.cs封装SearchByClassType方法,Class.Biz.cs封装FindSQLByType方法,以充分利用内嵌类“_”。
// Student.Biz.cs public static IList<Student> SearchByClassType(String type, SexKinds sex) => FindAll(_.ClassId.In(Class.FindSQLByType(type)) & _.Sex==sex); // Class.Biz.cs public static SelectBuilder FindSQLByType(String type) => FindSQLWithKey(_.Type==type);
填充数据
所有查询的本质,都是生成SQL跟DAL层执行,得到DbTable/DataSet/DataTable后,由实体类LoadData加载映射成为实体对象。
- IList<TEntity> LoadData(DbTable ds) 加载DbTable,XCode标配
- IList<TEntity> LoadData(DataSet ds) 传统
- IList<TEntity> LoadData(DataTable dt) 传统
- IList<TEntity> LoadData(IDataReader dr) 传统
因此,完全可以用其它方法查到数据集,然后经由LoadData加载,得到实体对象。
也可以自己实现 IDataRowEntityAccessor
接口,设置到 Meta.Factory.Accessor
来改变加载数据的行为。
高级查询
实体基类Entity<TEntity>内置了一套高级查询方法:
IList<TEntity> Search(DateTime start, DateTime end, String key, PageParameter page); WhereExpression SearchWhere(DateTime start, DateTime end, String key, PageParameter page);
该方法支持根据主时间字段所在区间,以及关键字,进行分页排序查询。
- 主时间。Meta.Factory.MasterTime,默认为第一个出现在索引首位的时间字段,一般是UpdateTime
- 关键字。默认构造为所有字符串字段的Contains操作,也就是 '%key%'
例如:
Select * From Node Where LastLogin>='2020-03-25 00:00:00' And LastLogin<'2020-03-26 00:00:00' And (Name Like '%win%' Or Code Like '%win%' Or Secret Like '%win%' Or Version Like '%win%' Or OS Like '%win%' Or OSVersion Like '%win%' Or MachineName Like '%win%' Or UserName Like '%win%' Or Processor Like '%win%' Or CpuID Like '%win%' Or Uuid Like '%win%' Or MachineGuid Like '%win%' Or MACs Like '%win%' Or InstallPath Like '%win%' Or Runtime Like '%win%' Or Address Like '%win%' Or LastLoginIP Like '%win%' Or CreateIP Like '%win%' Or UpdateIP Like '%win%' Or Remark Like '%win%') Order By ID Desc limit 20
自定义高级查询的更多用法请阅读