OF DataAccess数据访问组件开发指南
OF DataAccess是什么
数据访问处理一直以来是应用系统开发的核心之一,在.Net的DA发展历史中,有基于Ado.net的SqlHelper极简封装,有EntityFramework轻量级ORM DA,有语法优美的Linq to SQL/DataSet,还有更强大的舶来品Nhibernate、iBatis.Net等等。然而在老猿心中,这些,都不喜欢,那种基于原生模式的SQL语句调用模式,永远是心中的红牡丹或白牡丹。提供一套既原生亲切、又友好简便的ORM DA,便是OF DataAccess诞生的初衷。
OF DataAccess特点
- 对于单表(对应单实体对象)数据处理不需要手动编写SQL脚本,提供了基于拉姆达表达式的表达方法;该特点简化了对一些简单数据的处理,比如更新顾客状态,只需要一句:DAO.SO_Update<Customer>(new { CommonStatus = CommonStatus.DeActived }, null, f => f.LoginId == "uncleqin")即可。
- 对于多表操作或者较复杂的SQL语句,按照原生的SQL语句写出来,再放置到配置文件对应的节点中;这便于公司内部DBA Review,也方便即使需要切换DB类型时,由一个统一的地方修改,还有就是如果只是简单的调整一下SQL,不用重新编译程序发布,方便PS人员在线紧急处理。
- 高性能的ORM处理,最底层采用Dapper作为ORM核心;
- 内置实现了读写分离的处理,对于1 Master->N Slave模式下,可自动负载Slave DB,当然,你也可以自己提供负载策略。
- 各处可自定义的地方尽量基于接口,以方便你做自定义实现,以供不同情况下的扩展。
OF DataAccess用法
一、 配置文件
1. 配置文件的路径
(1)默认情况:在系统执行目录下创建一个叫Configuration的目录,再在该目录下创建一个叫DB的子目录,即:Configuration\DB,然后DA所有的配置文件都放在此子目录下。
(2)自定义目录:在App.config(winform)或者web.config(Web)的AppSettings下增加自定义节点:<add key="DBConfigFolder" value="MyConfig/DB"/>,key固定为DBConfigFolder,value支持相对路径和绝对路径,示例"MyConfig/DB"就是相对路径(系统执行目录下),如果value设置为"c:\dbconfig"这样即为绝对路径。
2. 配置文件说明
配置文件主要包括三种配置文件:DB.config, SingleObjectDB.config, 和一系列的SQL脚本配置文件。
- DB.Config是配置数据库连接方面的基本信息,以及对SQL脚本配置文件的注册;
- SingleObjectDB.config是针对单体对象(对应一个数据表)做的配置,如果你要用到DAO中的关于单体对象的处理,那么需要在这里配置该对象的数据库相关信息;
- SQL脚本.config,是存放SQL脚本的配置文件,建议在DB的子目录下再按照业务分目录管理。
(1) DB.config 文件示例及说明:
<DBConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <!--连接字符串配置:DBType支持SqlServer,MySql,Oracle--> <DBGroupList> <!-- DBGroup表示一组DB,一组DB可以由1个Master+N个Slave DB构成,也可以单独一个MasterDB构成; ConnKey=连接本组连接的别名,在SQL脚本配置中需要使用到; DBType=数据库类型,目前共三种SqlServer,MySql,Oracle,下面的示例均用SQL Server为示例。 --> <DBGroup ConnKey="TestDB" DBType="SqlServer"> <!-- Id=为链接取一个全局唯一身份编码,在DA内部使用; TimeOut=DB连接超时,单位秒; MasterDB=true表示为主库,每一组必须有且只能有1个主库,如果为false表示为从库,从库可以有多个,组件库会自动做负载均衡访问 --> <DBConn Id="Test-Master" TimeOut="60" MasterDB="true"> <ConnStr> <![CDATA[ data source=192.168.0.10\MasterInstance;database=TestDB;user id=jin;password=123456;connection reset=false;Timeout=30;connection lifetime=30; min pool size=0; max pool size=50 ]]> </ConnStr> </DBConn> <DBConn Id="Test-Slave1" TimeOut="60" MasterDB="false"> <ConnStr> <![CDATA[ data source=192.168.0.10\SlaveInstance1;database=TestDB;user id=jin;password=123456;connection reset=false;Timeout=30;connection lifetime=30; min pool size=0; max pool size=50 ]]> </ConnStr> </DBConn> <DBConn Id="Test-Slave2" TimeOut="60" MasterDB="false"> <ConnStr> <![CDATA[ data source=192.168.0.10\SlaveInstance2;database=TestDB;user id=jin;password=123456;connection reset=false;Timeout=30;connection lifetime=30; min pool size=0; max pool size=50 ]]> </ConnStr> </DBConn> </DBGroup> <DBGroup ConnKey="LogDB" DBType="SQLServer"> <DBConn Id="Log-Master" TimeOut="60" MasterDB="true"> <ConnStr> <![CDATA[ data source=192.168.0.11\MasterInstance;database=logDb;user id=sa;password=123456;connection reset=false;Timeout=30;connection lifetime=30; min pool size=0; max pool size=50 ]]> </ConnStr> </DBConn> <DBConn Id="Log-Slave1" TimeOut="60" MasterDB="false"> <ConnStr> <![CDATA[ data source=192.168.0.11\SlaveInstance1;database=logDb;user id=sa;password=123456;connection reset=false;Timeout=30;connection lifetime=30; min pool size=0; max pool size=50 ]]> </ConnStr> </DBConn> </DBGroup> </DBGroupList> <!--SQL脚本config文件列表,配置为DB文件夹下的相对路径--> <SQLFileList> <SQLFile>TestDB\SQLServer_Test.config</SQLFile> <SQLFile>LogDB\Log.config</SQLFile> </SQLFileList> </DBConfig>
(2) SingleObjectDB.config 文件示例及说明:
<?xml version="1.0" encoding="utf-8"?> <SingleObjectConfig> <SingleObjectList> <!--Name=对象(数据表)的名称; ConnKey=该对象属于哪个db链接组的ConnKey,参见DB.config; DBName=该数据表所属的数据库名称; --> <SingleObject Name="Customer" ConnKey="TestDB" DBName="TestDB" /> <SingleObject Name="Book" ConnKey="TestDB" DBName="TestDB" /> <SingleObject Name="Review" ConnKey="TestDB" DBName="TestDB" /> </SingleObjectList> </SingleObjectConfig>
(3)SQL脚本.config 文件示例及说明:
<?xml version="1.0" encoding="utf-8"?> <SQLConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SQLList> <!--1对1关联查询--> <SQL SQLKey="GetReview" ConnKey="TestDB" MasterDB="false" > <Text> <![CDATA[ SELECT top 1 * FROM TestDB.dbo.Review r INNER JOIN TestDB.dbo.Book b ON r.BookSysNo=b.SysNo WHERE r.SysNo=@sysno ]]> </Text> </SQL> <!--1对1列表查询--> <SQL SQLKey="GetReviewList" ConnKey="TestDB" MasterDB="false"> <Text> <![CDATA[ SELECT * FROM TestDB.dbo.Review r INNER JOIN TestDB.dbo.Book b ON r.BookSysNo=b.SysNo WHERE r.SysNo IN @SysNoes ]]> </Text> </SQL> <!--1-N-N的列表获取--> <SQL SQLKey="GetCustomerBooksAndReviewsList" ConnKey="TestDB" MasterDB="false"> <Text> <![CDATA[ SELECT * FROM TestDB.dbo.Customer c INNER JOIN TestDB.dbo.Review r ON c.SysNo=r.CustomerSysNo INNER JOIN TestDB.dbo.Book b ON r.BookSysNo=b.SysNo WHERE c.SysNo in @SysNoes ]]> </Text> </SQL> <!--一次返回多个记录集--> <SQL SQLKey="GetMultipleObject" ConnKey="TestDB" MasterDB="false"> <Text> <![CDATA[ SELECT TOP(1) * FROM TestDB.dbo.Customer WHERE SysNo=@CustomerSysNo; SELECT * FROM TestDB.dbo.Review WHERE CustomerSysNo=@CustomerSysNo; SELECT * FROM TestDB.dbo.Book WHERE InDate>@InDate; ]]> </Text> </SQL> <!--模板式分页查询--> <SQL SQLKey="QueryCustomerReview" ConnKey="TestDB" TimeOut="120"> <Text> <![CDATA[ SELECT r.SysNo,r.CustomerSysNo,r.BookSysNo,r.Title,r.InDate,c.Name,b.Title AS BookTitle,b.Price #FROM{[ TestDB.dbo.Review r INNER JOIN TestDB.dbo.Customer c ON r.CustomerSysNo = c.SysNo INNER JOIN TestDB.dbo.Book b ON r.BookSysNo=b.SysNo ]} #WHERE{[ <? c.LoginId like @LoginId ?> and <? r.indate>@start ?> and <? r.indate<@end ?> ]} #SORT{[ Order By c.SysNo ]} ]]> </Text> </SQL> </SQLList> </SQLConfig>
二、 OF DataAccess用法
OF DataAccess的Assembly为OF.Lib.DataAccess,所有的调用均从DAO这个类发起,DAO类提供了一系列的静态方法供业务层直接使用。由于OF DA采用了Dapper作为底层ORM内核,因此拥有一些Dapper的调用特性。
DAO提供了三大类静态方法:
1. 基于单个对象(SingleObject)的DAO方法说明:
基于单个对象(SingleObject)内部自动生成SQL脚本的简单CURD方法及ORM处理方法,方法名均以SO_开头,不支持分页查询;在更新、获取、删除方法中,支持以拉姆达表达式的形式设置条件。
(1) SO_Insert方法:单个对象(SignleObject)的数据插入,提供两个重载,一个是返回执行成功的条数,一个是返回创建成功后该数据的主键。
- int SO_Insert(object dataParameter, string excludeProperties, IDbTransaction transaction = null)
- PK SO_Insert<T, PK>(object dataParameter, string excludeProperties, IDbTransaction transaction = null)
- 入参说明:
* T:泛型,数据对象类型;
* dataParameters:数据实体,可以是T类型的实体,也可以是dynamic对象;
* excludeProperties:需要排除Insert的属性名称,多个用半角逗号分隔,忽略大小写;在创建时通常需要排除自增量的主键属性;
* transaction:事务对象。
- 示例代码:
int count = DAO.SO_Insert<Customer>(new Customer() { Name = "张三", LoginId = "so1", CommonStatus = CommonStatus.Actived } , "SysNo"); int sysNo = DAO.SO_Insert<Customer,int>(new { Name = "李四", LoginId = "so2", CommonStatus = CommonStatus.Actived }, "SysNo");
(2) SO_Update方法:单个对象(SignleObject)的数据更新,返回受影响行数。
- int SO_Update<T>(object dataParameter, string excludeProperties, Expression<Func<T, bool>> whereMatch, IDbTransaction transaction = null)
- 入参说明:
* T:泛型,数据对象类型;
dataParameters:数据实体,可以是T类型的实体,也可以是dynamic对象;
* excludeProperties:需要排除Update的属性,多个用半角逗号分隔,忽略大小写,通常需要排除自增量的主键属性、创建时间、创建人等,如果是 dynamic的对象,可设置本入参为null,因为dynamic对象只会更新动态设置的属性;
* whereMatch:基于SingleObject对象的拉姆达表达式,用来生成条件语句片段
* transaction:事务对象。
>* 示例代码:
int[] sysnoes = new int[] { 1, 2, 3 }; int count=DAO.SO_Update<Customer>(new { CommonStatus = CommonStatus.DeActived }, null, f => f.SysNo.DB_In(sysnoes) && (f.CommonStatus == CommonStatus.Actived || f.Name == "Jin"));
(3) SO_Load方法:单个对象(SignleObject)的数据加载,返回对象实体。
- T SO_Load<T>(Expression<Func<T, bool>> whereMatch, bool isMasterDB = true, IDbTransaction transaction = null)
- 入参说明:
* T:泛型,数据对象类型;
* whereMatch:基于SingleObject对象的拉姆达表达式,用来生成条件语句片段;
* isMasterDB:是否是读取Master DB的数据,默认是true;如果为fasle,则会从Salve DB中读取数据;
* transaction:事务对象。
>* 示例代码:
Customer c = DAO.SO_Load<Customer>(f => f.SysNo == 1);
(4) SO_GetList方法:单个对象(SignleObject)的数据列表获取。
- List<T> SO_GetList<T>(Expression<Func<T, bool>> whereMatch, bool isMasterDB = true, string orderBy = "", int? top = null, IDbTransaction transaction = null)
- 入参说明:
* T:泛型,数据对象类型;
* whereMatch:基于SingleObject对象的拉姆达表达式,用来生成条件语句片段;
* isMasterDB:是否是读取Master DB的数据,默认是true;如果为fasle,则会从Salve DB中读取数据;
* orderBy:排序要求;
* top:取前几条,为null表示不限制;
* transaction:事务对象。
>* 示例代码:
Customer cc = new Customer() { SysNo = 25, Memo = "用户memo" }; int len=3; List<Customer> list = DAO.SO_GetList<Customer>(f => f.Memo.DB_Like(cc.Memo) && f.LoginId.DB_Length() == len, true, "SysNo ASC");
(5) SO_Delete方法:单个对象(SignleObject)的数据删除,返回受影响行数。
- int SO_Delete<T>(Expression<Func<T, bool>> whereMatch, IDbTransaction transaction = null)
- 入参说明:
* T:泛型,数据对象类型;
* whereMatch:基于SingleObject对象的拉姆达表达式,用来生成条件语句片段;
* transaction:事务对象。
>* 示例代码:
int count = DAO.SO_Delete<Customer>(f => f.SysNo == 5);
2. 基于配置文件中原生Sql脚本调用的DAO方法说明:
基于配置文件中原生Sql脚本的数据访问及ORM处理方法,方法名均以Execute开头,一共有5个方法,分别是:
//2.1 执行返回受影响行数
T ExecuteEntity<T>(string sqlKey, object param = null, IDbTransaction transaction = null)
//2.2 执行返回首行首列值
T ExecuteEntity<T>(string sqlKey, object param = null, IDbTransaction transaction = null)
//2.3 执行返回主对象单个实体,支持到最多5个对象关联映射,关系为1-N,共6个重载。
TReturn ExecuteEntity<TFirst, TSecond, TReturn>(string sqlKey, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, string splitOn = SPLITON_FIELD)
//2.4 执行返回主对象列表,支持到最多5个对象关联映射,关系为1-N,共6个重载。
static List<TReturn> ExecuteEntityList<TFirst, TSecond, TReturn>(string sqlKey, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, string splitOn = SPLITON_FIELD)
//2.5 执行多条SQL语句,按照先后顺序返回
GridReader ExecuteMultiple(string sqlKey, object param = null, IDbTransaction transaction = null)
- 示例代码 (以上面示例的sql脚本为例)
//1-1的单个获取 Review review = DAO.ExecuteEntity<Review, Book, Review>("GetReview" ,(review, book) => { review.RefBook = book; return review; } ,new { sysno = 4 } ,null, "SysNo"); //1-1的列表获取 List<Review> list2 = DAO.ExecuteEntityList<Review, Book, Review>("GetReviewList" ,(review, book) => { review.RefBook = book; return review; } ,new { SysNoes = new int[] { 4, 5 } } ,null, "SysNo"); //1-N-N的列表获取 ctemp = null; List<Customer> clist = DAO.ExecuteEntityList<Customer, Review, Book, Customer>("GetCustomerBooksAndReviewsList" ,(customer, review, book) =>{ if (ctemp == null || ctemp.SysNo != customer.SysNo) ctemp = customer; if (book != null) ctemp.RefBooks.Add(book); if (review != null) ctemp.RefReviews.Add(review); return ctemp; } ,new { SysNoes = new int[] { 1, 2, 3 } } ,null, "SysNo"); //多个返回数据获取 var dg = DAO.ExecuteMultiple("GetMultipleObject", new { CustomerSysNo = 1, InDate = "2016-2-1 0:0:0" }); var customer = dg.Read<Customer>().FirstOrDefault(); var reviews = dg.Read<Review>().ToList(); var books = dg.Read<Book>().ToList();
3. 基于配置文件中分页查询脚本的DAO方法说明:
基于配置文件中Sql脚本分页查询的处理方法,仅有1个:QueryResult<T> PagedQuery<T>(string sqlKey, QueryFilter filter),为了减轻开发人员写分页查询的复杂程度,在Sql脚本中需要采用模板形式写脚本。
- 示例代码 (以上面示例的sql脚本为例)
//分页查询示例 QF_Review filter = new QF_Review() { LoginId = "Jin%", PageIndex = 0, PageSize = 10 }; QueryResult<QR_Review> result = DAO.PagedQuery<QR_Review>("QueryCustomerReview", filter); int count = result.TotalCount;
TODO List,敬请关注!
- 目前还不支持分布式事务的处理,可在下一步加上;
- Oracle的SingleObject系列功能和分页模板查询还没实现(接口里全是throw new NotImplementedException();),我们会逐步完善,有兴趣的朋友也可以加入;
- 下一步还要考分布式RLDB的切片处理,可考虑基于Antlr做词法分析来进行分布式透明化处理。
代码地址:码云 OFProject