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特点

  1. 对于单表(对应单实体对象)数据处理不需要手动编写SQL脚本,提供了基于拉姆达表达式的表达方法;该特点简化了对一些简单数据的处理,比如更新顾客状态,只需要一句:DAO.SO_Update<Customer>(new { CommonStatus = CommonStatus.DeActived }, null, f => f.LoginId == "uncleqin")即可。
  2. 对于多表操作或者较复杂的SQL语句,按照原生的SQL语句写出来,再放置到配置文件对应的节点中;这便于公司内部DBA Review,也方便即使需要切换DB类型时,由一个统一的地方修改,还有就是如果只是简单的调整一下SQL,不用重新编译程序发布,方便PS人员在线紧急处理。
  3. 高性能的ORM处理,最底层采用Dapper作为ORM核心;
  4. 内置实现了读写分离的处理,对于1 Master->N Slave模式下,可自动负载Slave DB,当然,你也可以自己提供负载策略。
  5. 各处可自定义的地方尽量基于接口,以方便你做自定义实现,以供不同情况下的扩展。

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

posted @ 2018-01-11 14:40  OF_Project  阅读(323)  评论(0编辑  收藏  举报