(c#)DDL\DML类库-数据库操作从来没有如此简单

一直都对ORM框架提不起兴趣,就同当时听讲Java MyBatis的老师所说,他没法在一致性上解决查询的问题(当然MyBatis如同hibernate一样让我产生了厌恶感-代码与sql分离会让我觉得更难过-这也是我一直难以接受html-template和php这种混入语言的原因)。

在从IFormatProvider获得灵感后,TeaDog(小而美,有效行数1w+行)已经解决了很多棘手的问题,也让数据库编程变得更简单:

  • 想痛痛快快的写sql吧,我来负责参数化
  • 既然标记了参数类型,就让我帮你把它转换好
  • 老兄我们该对JSON做好支持
  • like的参数化、in的参数化总是让很纠结?
  • SQL条件拼接和参数化至此变得不那么格格不入!
  • 我不喜欢ORM,是因为我能让oracle语法在mysql中运行良好,反之亦然
  • 常用封装,oracle自增长id问题、分页问题
  • DataTable、Model、JSON的相互映射和批量入库
  • 启动事务、超时设置

原生SQL

不啰嗦,请看例子:

dml.Excute(
@"insert into 
structTestTable(id,name,dateBirth,height,weight,memo,other) 
values({0:i},{1:s},{2:t},{3:f},{4:d},{5:c},{6:b})",
1, "山", new DateTime(1986, 11, 18), 163.2f, 165.7, "我是中国人", new byte[] { 1, 2 });

是不是和平时写SQL语句一样?答案是,有些区别。语句中扩展了识别参数类型的部分:

预定义参数(下面称标记)

参数 释义 映射类型 说明
i 整数 int
s 字符串 string 没用标记时的默认值
t 日期 datetime
f 浮点 float
d 双精度浮点 double
c 长文本 string 建议显示声明
b 长二进制 byte[] 大部分只有这个需要标记
! 不解析 例子见下文

标记 t
支持常见的日期格式都可以转换,如:
2010/10/1 12:00:00
2010-10-1 12:00:00
2010年10月1日 12时0分0秒
2010\10\1 12:00:00
以整形int表示的表示由1970年1月1日0时0分0秒0毫秒到某个时间的天数
以长整形long表示的由1970年1月1日0时0分0秒0毫秒到某个时间的100纳秒数
DateTime类型以及以上字符串的日期格式

标记 c
有oracle开发经验的猿们应该都知道超过2000个长度的汉字保存到varchar2(4000)的字段中会出错,悲剧的是就算字段是clob,指定数据类型为varchar2时程序同样会出错,所以如果字段设计成clob,这个标示无疑是必须的。

标记 b
不管什么数据类型,如果想保存为二进制就标记为b吧。

自动类型转换

类库对参数的处理,有几个关键步骤:
1.对已标记的类型将目标参数进行对应的转换;
2.过对未标记的数据类型进行自动识别;
3.对未标记又无法识别的类型转换为其字符串形式;

那么下面的sql现在也可以正常入库了,自动转换类型后和上面的句子是一样的效果。

dml.Excute(
@"insert into 
structTestTable(id,name,dateBirth,height,weight,memo,other) 
values({0},{1},{2:t},{3},{4},{5:c},{6:b})",
"1", "", "1986-11-18", "163.2f", "165.7", "我是中国人", "12");

JSON的支持

支持从JSON中直接取数据,上代码:

using Newtonsoft.Json.Linq;
JObject obj=new JObject(new JProperty("id","1"),new JProperty("name","山"));
JArray arr=new JArray(obj);
//通过@propertyName取出propertyValue,也可以通过@index取出数组中对应下标的记录
dml.Excute(
@"insert into structTestTable(id,name,memo) 
values({0:i@id},{0:i@name},{1:@0@name})",obj,arr);

将sql-select的结果转换为JSON

//!指向的字符串会直接拼接,而不参数化,用于一些代码中的常量
JArray arr = dml.QueryToJArray("select id from {0:!}  where 1=1", "structTestTable");

//或者直接返回一条数据
JObject obj = dml.QueryToJObject("select id from {0:!}  where id=1", "structTestTable");

通过Model+json增删改数据

JArray arr=new JArray({...},{...});
//这是最简单的情况,直接根据JArray插入数据
//类库还可以根据arr中的状态字段_rowState(指定的名称)处理增删改混合的情况
int modified=dml.UpdateDataSet<Model>("structTestTable",arr,DMLOperator.Insert);
//单行数据的插入,同样还有单行数据的更新
JObject obj=new JObject(propertyA,protertyB);
modifyied=dml.Insert<Model>(obj);

如果不想定义Model怎么办呢,程序会通过JObject中的属性名做简单推导(见批量更新章节)。

JArray arr=new JArray({...},{...});
//如果是更新或增删改都有,请指明第四个参数,主键数组string[]keys
int modified=dml.UpdateDataSet("structTestTable",arr,DMLOperator.Insert);

SQL拼接VS参数化

系统通过调用链(DSL)实现字符串的拼接,分别提供SelectBuilder,InsertBuilder,DeleteBuilder,UpdateBuilder4个类提供字符串的拼接。

string sql1 = "select * from structTestTable";
QueryBuilder qb = new QueryBuilder(sql1);
JObject filter=...
//push-pop-popAll提供括号控制优先级
//andor最后一个参数都是对条件的判断,条件成功后表达式会被添加到sql中
//表达式中可以照常使用{0}参数化,依次给出0,1,2,3即可,程序会处理无效的参数
qb.Where()
.Push()
.Push()
.And("type = 1",filter["type"]!=null)
.Or("id={0}",filter["id"],filter["id"]!=null)
.Pop()
.And("1=1")
.PopAll();
JArray arr=dml.QueryToJArray(qb.sql,qb.args);

其他3个命令的代码这里不给出了,设计思想是一样的,通过最后一个参数给出的真假值判断是否对sql拼接。

SQL语句兼容

其实也没有那么神器,自动转换并不是通过对sql进行语法分析实现的,只是做了函数和操作符的映射。不过我想95%(粗劣估计)的sql语句的兼容性是可以解决掉了。

首先,通过一个func.config文件对源数据库的函数和操作符进行定义,定义格式大家都很熟悉,如:

函数定义

例子1:常规函数定义
oracle的instr方法,参数定义为 p0,p1[,p2[,p3]]

例子2:关键字分割的函数,其中@为前导标记为
如case函数

<add name="case" params="@ [p0] when  p1 then p2 [when p3 then p4 [...]] [else p5] end "/>

还有cast函数

<add name="cast" index="1" params="@(p0 as char[(p1)])"/>

例子3:符号的定义,也是用@做前导符
连接符||

<add name="||" params="@p0||p1[||...]"/>

函数转换

简单函数的转换规则是同名参数替换。复杂情况下需要对可选参数列表进行处理,如:

例子1:常规转换

<add name="concat" matchCondition="3" rename="convert" body="(concat(p0,p1),p2)"  />

其中可以对参数的匹配情况进行定义,如上匹配concat函数指定了3个参数(matchCondition=”3”)时映射方式

例子2:可选参数列表
以下是Oracle中的decode方法转换到MySql的case方法的定义:

<add name="decode" rename="case" body=" p0 when  p1 then p2 [when p3 then p4 [...]] [else p5] end " />

例子3:匹配不成功的情况
对于Oracke中Instr提供4个参数时,Mysql可能是不够完全转换的,这时候使用者可以去数据库中自定义Function解决,然后完善映射规则;
同时抛出异常的可以提醒使用者是否换其他的处理方式;另外一个好处是,如果有一些禁止使用的函数,也可以通过这种方式定义;

<add name="instr"  matchCondition="4" throw="1" />

like、in的使用

JArray arr = dml.QueryToJArray(
@" select id from structTestTable  where name like {0} and id in {1}",
dml.like("张三"),dml.in(new int[]{1,2,3}));

自增序列

严格的说Oracle不支持自增字段。通过序列+触发器可以勉强的实现,但是还有另外一个问题就是无法取回增长的编号。针对这些不兼容因素,类库中提供了3种解决id的办法:
1.通过全局GUID,有点粗暴,但是很有效;
2.程序兼容的自增长列的逻辑;(Oracle还是要新建序列,其他数据库用自增属性);
3.数据库端的自增长(Oracle序列+触发器,其他数据库用自增属性);

DateTime birth = new DateTime(1986, 11, 18);
TeaDog.Factory.Sequence seq = dml.Sequence("xl_structTestTable_ID");
Assert.AreEqual<int>(dml.Excute("insert into structTestTable(id,name,dateBirth,height,weight,memo,other) values({0},{1},{2},{3},{4},{5:c},{6:b})", seq, "张三", birth, 163.2f, 165.7, "山", new byte[] { 1, 2, 3, 4 }), 1);

后面章节中还会结合模型Model的使用来讲解自增id的具体使用方法。

分页问题

分页也是通过封装的方式处理,作为Sql语句可选参数的附加参数进行解析处理。

dml.Query("select * from structTestTable", new TeaDog.Factory.PaginationEx(2,10,"id"));

DataTable批量更新

类库提供的批量更新,支持直接入库、数据列表入库。带有数据模型的批量更新,更新的操作依据是模型的属性。JSON数据入库则通过JSON对象第一行数据的属性命名方式进行分析(其中以_开头的属性会被排除,_rowstate是行的更新操作,其他字段会被识别为数据表列)。
DMLOperator可以接受的值包括DMLOperator.All,或者DMLOperator.Insert、DMLOperator.Update,DMLOperator.Delete,不接受任意两个的组合。当值为All时,系统根据数据中的_rowstate修正数据的更新状态,当指定其他的任意操作时,系统会将所有数据更新为对应的状态,省去用户去设置_rowstate的过程;
由于方法对数据一次进行处理,方法最后提供了用户干预数据处理的模块,dataAdapter会传的数据包括,数据的索引,数据本身,数据属性;adapterFields 用于指定用户干预的字段。

int UpdateDataSet(string tableName, List<object> ms, DMLOperator operation = DMLOperator.All, string[] keyFields = null, Func<int, object, string, object> dataAdapter = null, string[] adapterFields = null);
int UpdateDataSet<T>(string tableName, List<T> ms, DMLOperator operation = DMLOperator.All, string[] keyFields = null, Func<int, T, string, object> dataAdapter = null, string[] adapterFields = null) where T : DMLModel<T>;
int UpdateDataSet<T>(string tableName, JArray arr, DMLOperator operation = DMLOperator.All, string[] keyFields = null, Func<int, JObject, string, object> dataAdapter = null, string[] adapterFields = null) where T : DMLModel<T>;
int UpdateDataSet(string tableName, JArray obj, DMLOperator operation = DMLOperator.All, string[] keyFields = null, Func<int, JObject, string, object> dataAdapter = null, string[] adapterFields = null);

超时设置

类库未提供具体超时时间长短的设置,只提供了是否设置超时的设置。

bool excuteNoTimeout { get; set; }

开始使用

1.见下图,首先我们需要复制几个配置文件。
func.config
用于实现不同数据库见函数映射的文件,需要支持sql语法转换请提供此文件;
其配置本文件中的喜好属性preference,他代表项目中会使用的sql语法。一个项目应使用统一的语法规则。
provider.config
用于定义标签对应的数据类型,请确定connection.config中使用的程序集在此文件中提供了映射。
这里写图片描述
对了,以上两个文件的名字如果大家不介意也不用在项目的配置文件中去映射他,程序会自动从项目的根目录查找这两个文件并初始化配置。
2.引入类库(dll在.net Framework 4.5下编译)
TeaDog
Microsoft.Practices.EnterpriseLibrary.Common
Microsoft.Practices.EnterpriseLibrary.Data
Newtonsoft.Json.dll
Tools(也是本人写的)
3.可以开始使用了,下面提供一个完整的例子

DateTime dbirth = new DateTime(1988, 11, 18);
JObject job = new JObject(new JProperty("id", 1), new JProperty("name", "山"), new JProperty("b", dbirth));
using (DXL dml = new DXL("oracleManaged", true))
{
    int ret = dml.Excute("insert into structTestTable(id,name,dateBirth) values({0:@id},{0:@name},{0:@b})", job);
    Assert.AreEqual<int>(ret, 1);
    DataRow record = dml.Query("select id,name,dateBirth from structTestTable where id=1").FirstRow();
    Assert.IsNotNull(record);
    Assert.AreEqual<int>(record.CellValue<int>("id", -1), 1);
    Assert.AreEqual<string>(record.CellValue<string>("name"), "山");
    Assert.AreEqual<DateTime>(record.CellValue<DateTime>("dateBirth"), dbirth);
}

资源下载

https://pan.baidu.com/s/1Cp97DkRYmqobT74E2UfCeA
资源内容包括:需要的dll,以及少量但是必要的单元测试代码。使用文档,请直接F12查看dll中的注释。
本人编写的dll目前通过加密处理的,并且这个版本的事务是关闭的,基本不能用于实际生产。如果有需要源码或者完整版本的请文后邮箱联系我。

写在最后

本人正在求职状态,北京工作马上结束,希望在西安市找一份工作。本人工作经验9年,技术开发9年,管理经验5年,培训经验3年;精通语言c++、c#、JavaScript、NodeJs全栈。酷爱编程,单项目单人有效代码行数最高记录4.3w行(c++项目)。

请联系我:[email protected]
2018.09.10

猜你喜欢

转载自blog.csdn.net/ninao99/article/details/82017518