zoukankan      html  css  js  c++  java
  • 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的子目录,即:ConfigurationDB,然后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.10MasterInstance;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.10SlaveInstance1;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.10SlaveInstance2;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.11MasterInstance;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.11SlaveInstance1;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>TestDBSQLServer_Test.config</SQLFile>
        <SQLFile>LogDBLog.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

  • 相关阅读:
    android开发中如何开启用户安装的应用程序?
    丑数查找算法
    session.save_path目录大量session临时文件带来的服务器效率问题
    MOSS点滴(1):如何开发和部署feature
    如何将Excel中两个单元格或两列中的数据合并
    如何在 MOSS 2007 启用 Session
    MOSS LIST的一些属性说明
    国外广播电台
    Excel 导出 按钮
    在文档库或 Windows SharePoint Services SharePoint Portal Server 中创建一个新的文件夹或新文档时,您会收到一个"指定的文件或文件夹名太长"错误消息
  • 原文地址:https://www.cnblogs.com/ofproject/p/8268309.html
Copyright © 2011-2022 走看看