什么是EasySql

在我们早期写的代码中,想实现组装灵活的sql语句与参数,我们可以去翻阅早期自己写的代码

var @sb = new StringBuilder();
sb.Append("select a.* from [user] as a");
if(参数1 !=null)
{
sb.Append("where Id = '参数1'");
appendWhere = true;
}
if(参数2 !=null)
{
if(appenWhere)
{
sb.Append("and Name = '参数2'");
}
else
{
sb.Append("where Name = '参数2'");
appendWhere = true;
}
}

现在回来看是不是非常无语?并且像这种代码,咱还真写得不少。

所以我想要一种可以根据参数去动态构造sql语句的项目。有人推荐IBatisNet,使用的1年内,我想很多人也会出现这样的代码:

  <alias>
<typeAlias alias="Queue" type="B2C.Models.Queue,B2C.Model" />
</alias>

还有TypeHandler,ResultMap,ParameterMap等,这种标签的存在也限制很多东西。因此我想可以使用模板方式,去掉这种标签。当时有t4,xml,对比之下选择了xml。

开始一个项目,作为架构者,应该从使用者的角度上考虑怎么使用该API的。

1:考察CRUD方式,我们不难发现基本都是如下代码的使用形式

<delete id="delUser">
delete from myuser
where UserId = @UserId;
</delete> <update id="updUser">
update myuser
set UserName = @UserName
where UserId = @UserId;
</update> <insert id="insUser">
insert into myuser(UserId,UserName)
values(
@UserId,
@UserName);
<return type="int">
select @@identity;
</return>
</insert> <select id="qryUser">
select a.* forom myuser as a
<if then="where" end=";" split="">
<ifnotnull parameter="UserId" then="and">
UserId = $UserId$ and UserId = @UserId
</ifnotnull>
<ifnotnull parameter="Id" then="and ">Id = @Id</ifnotnull>
<ifempty parameter="UserName" then="and">
UserName = '111'
</ifempty>
<ifnotempty parameter="UserName" then="and">
UserName = '222'
</ifnotempty>
<ifarray parameter="IdArray" then="and" split="," open="" close="">
Id in (@IdArray)
</ifarray>
</if>
</select>

这样可以确保根据参数动态构造sql语句,基于一些标签是可以重复用的,我们将这些标签设为sql标签

  <sql id="qryUserContion">
<ifnotnull parameter="UserId" then="and">
UserId = $UserId$ and UserId = @UserId
</ifnotnull>
<ifnotnull parameter="Id" then="and ">Id = @Id</ifnotnull>
<ifempty parameter="UserName" then="and">
UserName = '111'
</ifempty>
<ifnotempty parameter="UserName" then="and">
UserName = '222'
</ifnotempty>
</sql>

这样下面的select就可以引用该标签了

  <select id="qryUser">
<include refid="sqlUserAllField"></include>
<if then="where" end=";" split="">
<include refid="qryUserContion"></include>
<ifarray parameter="IdArray" then="and" split="," open="" close="">
Id in (@IdArray)
</ifarray>
</if>
</select>

我们来看看其xml.xsd(schame,整个xml语法的定义)

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema targetNamespace="never.easysql"
elementFormDefault="qualified"
xmlns:mstns="http://tempuri.org/XMLSchema.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="never.easysql"> <xs:element name="namespace">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element ref="sql" maxOccurs="unbounded" />
<xs:element ref="select" maxOccurs="unbounded" />
<xs:element ref="delete" maxOccurs="unbounded" />
<xs:element ref="update" maxOccurs="unbounded" />
<xs:element ref="insert" maxOccurs="unbounded" />
<xs:element ref="procedure" maxOccurs="unbounded" />
</xs:choice>
<xs:attribute name="id" type="xs:string" use="required" />
<xs:attribute name="indented" type="xs:boolean" />
</xs:complexType>
</xs:element> <xs:element name="procedure">
<xs:complexType mixed="true">
<xs:attribute name="id" type="xs:string" use="required" />
<xs:attribute name="indented" type="xs:boolean" />
</xs:complexType>
</xs:element>

可以知道xml下的namespace节点应该只有sql,select,delete,update,insert,procedure这6个节点了。namespace节点都有id与indented属性,Id属性是必须的,用过区分所有节点(跟身份证一个原理,只不过这个Id只在当前Provider中要求唯一,不同的provider可以包含不同的xml文件),indented表示缩进,会将多余的回车与换行替换掉,让整个sql语句更加好看而(使用过程出现一些特别问题的可以直接不要缩进就好了)。

2:加载xml文件

使用SqlTagProvider类去加载特定的xml文件,并且将期分析得到不同的sqlTag

看看SqlTagProvider.Load方法

 public SqlTagProvider Load(Stream stream, string filename = null)
{
var doc = new System.Xml.XmlDocument();
doc.Load(stream);
var @namespaces = doc["namespace"];
if (@namespaces == null)
return this; var idele = namespaces.Attributes.GetNamedItem("id");
var indentedele = namespaces.Attributes.GetNamedItem("indented");
var id = idele == null ? "" : idele.Value;
var indented = indentedele == null ? true : indentedele.Value.AsBool(); var next = @namespaces.FirstChild;
while (next != null)
{
if (next.NodeType == System.Xml.XmlNodeType.Comment)
{
next = next.NextSibling;
continue;
} var name = next.Name;
var nextIdele = next.Attributes.GetNamedItem("id");
if (nextIdele == null)
throw new Never.Exceptions.KeyNotExistedException("can not find the id atrribute in this {0} file", filename); var nextId = nextIdele.Value;
if (nextId.IsNullOrEmpty())
throw new Never.Exceptions.DataFormatException("can not find the id atrribute in this {0} file", filename); if (this.sortedSet.ContainsKey(nextId))
throw new DataFormatException("the {0} is duplicated", nextId); var sqlTag = new SqlTag();
sqlTag.Id = nextId;
sqlTag.NameSpace = id;
sqlTag.IndentedOnNameSpace = indented;
if (!LoadCommandName(next, sqlTag))
{
next = next.NextSibling;
break;
}
sqlTag.Node = next;
this.sortedSet[nextId] = sqlTag;
next = next.NextSibling;
} return this;
}

分析xml下所有节点,得到所有的sqlTag,这个sqlTag是什么东东?

    /// <summary>
/// sqltag
/// </summary>
public class SqlTag
{
#region prop /// <summary>
/// Id
/// </summary>
public string Id { get; internal set; } /// <summary>
/// 命令空间
/// </summary>
public string NameSpace { get; internal set; } /// <summary>
/// 命令空间是否使用缩进信息
/// </summary>
public bool IndentedOnNameSpace { get; internal set; } /// <summary>
/// 自身是否使用缩进信息
/// </summary>
public bool IndentedOnSqlTag { get; internal set; } /// <summary>
/// 命令
/// </summary>
public string CommandType { get; internal set; } /// <summary>
/// 节点
/// </summary>
public XmlNode Node { get; internal set; } #endregion prop #region label /// <summary>
/// 所有节点
/// </summary>
public List<ILabel> Labels { get; set; } /// <summary>
/// 所有节点总长度
/// </summary>
private int TextLength { get; set; } /// <summary>
/// 是否格式化
/// </summary>
private bool formatLine
{
get
{
if (!this.IndentedOnSqlTag)
{
return false;
} return this.IndentedOnNameSpace;
}
} #endregion label
}

原来是我们刚才上面说的到“sql,select,delete,update,insert,procedure”这6个节点的对象。因此是否可以得出,后面通过Id得到sqlTag后使用Format方法得到执行参数与Sql语句呢?对啦,就是这样了

3:参数标签

通过xml的schame我们可以得知参数标签一共有“innotnull,ifnull,ifnotempty,ifempty,ifcontain,ifnotexists,ifarray" 7种参数控制标签,还有"include,return,if,text“ 4种流程与内容标签。

在说7种参数控制标签之前,我们要引入一个问题:如果传入一个可空类型的参数int? a这一种情况下,那么是认为a这个参数是当正常传入还是看其hasValue才能传入?为了解决这种问题,eqsysql引用了可空参数

    /// <summary>
/// 可空类型
/// </summary>
public interface INullableParameter
{
/// <summary>
/// 是否有值,如果是Guid类型的,则跟Guid.Empty比较,如果是string类型的,则与string.Empty比较,如果是可空类型的int?,则看是否有值 ,如果是数组的,则看数组是否可有长度
/// </summary>
bool HasValue { get; }
} /// <summary>
/// 可空类型
/// </summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public interface IReferceNullableParameter : INullableParameter
{
/// <summary>
/// 值
/// </summary>
object Value { get; }
}

则对int?a 这种参数,如果hasValue  == true 就表示可以传入参数,hasVlue == false 表示不传。可空参数只对基元类型 + string + guid生效

7种参数控制标签

  1. ifnotnull  如果参数Key不存在,则不用该标签,否则使用该标签则要满足:(1)如果是可空参数并且hasValue  == true;(2)不是可空参数,并且Value不是null
  2. ifnull   如果参数Key不存在,则不用该标签,否则使用该标签则要满足:(1)如果是可空参数并且hasValue  == false;(2)不是可空参数,并且Value是null;注意哦,Value已经是null了,sql语句就要小心使用使用Value的值了,比如如下的代码
    <ifnull parameter="UserName" then="and">
    UserName = @UserName
    </ifnull>

    UserName会传入一个DbNull.Value对象,此时还不如使用ifcontain标签

  3. ifnotempty  跟ifnotnull一样,不过只是针对string与guid(因为这2种类型定义了String.Empty与Guid.Empty)
  4. ifempty  跟ifnull一样,也是只针对string与guid
  5. ifcontain   如果参数Key不存在,则不用该标签,否则使用该标签则要满足:(1)如果是可空参数并且hasValue == true就使用标签,(2)key存在
  6. ifnotexists 跟ifcontain相反
  7. ifarray 数组参数,使用条件分2种:(1)如果没有验证参数名字,则表示使用的是整个数组,这种情况多使用批量插入;(2)有验证参数名字的,跟ifnotnull相同的使用条件,sql语句会将该参数变成最上面说到的@Id1,@Id2,@Id3这种形式;

4种流程与内容标签

  1. return 用于执行sql后返回的第一个值(故有return标签的方法应该是使用ado.net里面的ExecuteScalar方法),基本用于单条insert语句用于获取自增Id。return的返回类型只是有限制的,当前是byte,char,datetime,decimal,doublefloatint,long,short,guid,string,other
  2. include 用于各种嵌套,方便少写一些标签而已,比如获取分页与记录的where条件应该是一样的,我们可以将where的语句抽象得出一个sql标签,被select 直接Include了(注意,不同xml文件的标签不能include,否则报xml节点错误的bug)
  3. if 大多数if下面会包含多种7种参数标签或text标签,这实际就是一个容器而已;如果里面有一个标签生效,那么就会在这些标签format内容之前加入if里面的then内容,每成功format一个标签内容后,会加入if里面的split内容。
  4. text 就是我们使用的sql文本内容了,对应xml是innerText节点

加载xml都要分析参数所在的位置,所以我们应该将这些xml分析好后缓存起来用于提高性能。参数分@参数还是$$参数,@参数是将值传到commandParameter里面去,$$参数是直接变成字符串被format到内容里面,会存在注入的危险,尽量少用$$参数

4:事务

  easysql的事务是使用了ThreadLocal的技术实现的,

System.Threading.ThreadLocal<ISession> currentSessionThreadLocal

在BeginTransaction后会在currentSessionThreadLocal变星设置一个Session对象,该Session包含如下属性

    /// <summary>
/// 执行session,只有开了事务才不为空
/// </summary>
public interface ISession : System.IDisposable
{
/// <summary>
/// 数据库相关源
/// </summary>
IDataSource DataSource { get; } /// <summary>
/// 事务
/// </summary>
IDbTransaction Transaction { get; } /// <summary>
/// 数据操作接口
/// </summary>
IDao Dao { get; }
}

在RollBackTransaction与CommitTransaction会将currentSessionThreadLocal变量设置为null.

        /// <summary>
/// 开始事务
/// </summary>
/// <param name="isolationLevel">事务等级</param>
/// <returns></returns>
public virtual ISession BeginTransaction(IsolationLevel isolationLevel)
{
if (this.CurrentSession != null)
{
return this.CurrentSession;
} this.CurrentSession = new DefaultSession()
{
Transaction = ((IEasySqlTransactionExecuter)this.SqlExecuter).BeginTransaction(isolationLevel),
DataSource = DataSource,
Dao = this,
}; this.CurrentSessionThreadLocal.Value = this.CurrentSession;
return this.CurrentSession;
} /// <summary>
/// 回滚事务
/// </summary>
/// <param name="closeConnection">是否关闭链接</param>
public virtual void RollBackTransaction(bool closeConnection)
{
if (this.CurrentSession != null)
{
((IEasySqlTransactionExecuter)this.SqlExecuter).RollBackTransaction(closeConnection);
this.CurrentSessionThreadLocal.Value = null;
} this.CurrentSession.Dispose();
this.CurrentSession = null;
}

5 其它

  1. IDao接口中的GetSqlTagFormat可以得到整个sql语句
  2. EmbeddedDaoBuilder是处理将xml文件作为嵌入资源(配合里面的GetXmlContentFromAssembly方法直接读取*.dll里面的xml文件),而FileDaoBuilder是查询xml文件所在地
  3. IDao 扩展为TextDao下跟sqlclient的使用一样,都是直接写sql语句+参数;扩展为XmlDao后,传入参数再执行insert等操作,只是方便使用而已(比如释放资源,将参数放在前面,后面update("id")不用传参数而代码变得好看一点)
  4. 关于事务中的System.Threading.ThreadLocal<ISession>对象,因为daobuilder生命周期大多数都是实例化后被保存为某个引用(可理解为每个daobuilder只实例化一次), 里面的build回调方法都是生产一个新的dao,当开了事务后同线程使用ThreadLocal得到相同的dao,有人会担心ThreadLocak的List<Sesssion>对象在是否会出现不释放容量的情况下导致内存溢出。
  5. 与sqlclient数组参数区别:sqlclient是根据参数是否为数组去拆分为@Id1,@Id2等,而easysql是根据标签认为是该参数是数组参数,如果在easysql数组标签传入的不是数组,则抛异常。

文章导航:

  1. never框架
  2. sqlcient 一套容易上手性能又不错的sqlhelper
  3. ioc工具easyioc

never下的easysql的更多相关文章

  1. C++程序结构---1

    C++ 基础教程Beta 版 原作:Juan Soulié 翻译:Jing Xu (aqua) 英文原版 本教程根据Juan Soulie的英文版C++教程翻译并改编. 本版为最新校对版,尚未定稿.如 ...

  2. never下ioc

    生命周期 当前分单例,作用域(范围),短暂.单例是整个服务中只有一个实例,短暂则是每一次得到的都是新的实例,作用域就是在该一套行动中内得到的是同一个实例,该行动中指的是什么?我们看看demo下的sta ...

  3. Android SwipeRefreshLayout 下拉刷新——Hi_博客 Android App 开发笔记

    以前写下拉刷新 感觉好费劲,要判断ListView是否滚到顶部,还要加载头布局,还要控制 头布局的状态,等等一大堆.感觉麻烦死了.今天学习了SwipeRefreshLayout 的用法,来分享一下,有 ...

  4. IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法

    直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...

  5. Ubuntu下使用nvm

    写在前面:刚写着写着博客就跨年了,希望新的一年大家万事如意,一切向"前"看! 安装 wget -qO- https://raw.githubusercontent.com/crea ...

  6. Cmder--Windows下命令行利器

    cmder cmder是一个增强型命令行工具,不仅可以使用windows下的所有命令,更爽的是可以使用linux的命令,shell命令. 安装包 安装包链接 下载后,直接解压即用. 修改命令提示符λ为 ...

  7. NodeJs在Linux下使用的各种问题

    环境:ubuntu16.04 ubuntu中安装NodeJs 通过apt-get命令安装后发现只能使用nodejs,而没有node命令 如果想避免这种情况请看下面连接的这种安装方式: 拓展见:Linu ...

  8. GreenDao 数据库:使用Raw文件夹下的数据库文件以及数据库升级

    一.使用Raw文件夹下的数据库文件 在使用GreenDao框架时,数据库和数据表都是根据生成的框架代码来自动创建的,从生成的DaoMaster中的OpenHelper类可以看出: public sta ...

  9. [APUE]UNIX进程的环境(下)

    一.共享库 共享库使得可执行文件中不再需要包含常用的库函数,而只需在所有进程都可存取的存储区中保存这种库例程的一个副本.程序第一次执行的时候或第一次调用某个库函数的时候,用动态链接方法将程序与共享库函 ...

随机推荐

  1. 扩展你的javascript数组

    如今做的项目用的正是jquery的框架,Jquery miniui,其功能强大.性能卓越.易于上手.不失灵活,在不断学习和研发的过程中,miniui给了非常多的启示,让我又一次认识了js的本质,意识到 ...

  2. Qt 自定义事件(三种方法:继承QEvent,然后Send Post就都可以了,也可以覆盖customEvent函数,也可覆盖event()函数)

    Qt 自定义事件很简单,同其它类库的使用很相似,都是要继承一个类进行扩展.在 Qt 中,你需要继承的类是 QEvent. 继承QEvent类,你需要提供一个QEvent::Type类型的参数,作为自定 ...

  3. 贝叶斯推理(Bayes Reasoning)、独立与因式分解

    P(X,Y)=P(X)P(Y),X⊥Y P(X,Y,Z)∝ϕ1(X,Z)ϕ2(Y,Z),(X⊥Y∣∣Z) 1. Reasoning patterns causal reasoning 由原因到结果的一 ...

  4. 2-2 Consul注册注销流程

    铺垫,创建健康检查方法,Consul服务器隔一段时间请求一下webapi里的一个方法,如果这个方法没有问题,则证明这个webapi还在正常工作,这个webapi提供的服务就存在.如果方法没有返回,或者 ...

  5. Codeforces 15C Industrial Nim 简单的游戏

    主题链接:点击打开链接 意甲冠军: 特定n 下列n行,每一行2的数量u v 表达v礧:u,u+1,u+2···u+v-1 问先手必胜还是后手必胜 思路: 首先依据Nim的博弈结论 把全部数都异或一下, ...

  6. 第1讲:The nature of Testing--測试的本质

    *********声明:本系列课程为Cem Kanner的软件黑盒測试基础的笔记版************** What's A COMPUTER PROGRAM? Textbooks often d ...

  7. thinkphp3.2定义多模块并设置默认模块

    前台入口文件index.php <?php // +---------------------------------------------------------------------- ...

  8. ATS项目更新(3) 远程同步到执行机器

    1: echo %time% 2: 3: 4: rem ** ipc and mapping 5: c: 6: net use x: /del 7: net use y: /del 8: net us ...

  9. windows8运行zxing源码 生成与解码二维码 详解(含注释与图解可直接运行)

    1 下载zxing2.1 2 本代码配置环境:eclipse.java1.6.windows8.zxing2.1 3 解压后将文件夹里面core/src下面的com文件夹导入到eclipse工程(工程 ...

  10. Coder-Strike 2014 - Finals (online edition, Div. 1)

    CF 420A  A. Start Up 题目链接: http://codeforces.com/problemset/problem/420/A 题目意思: 给一个字符串A,通过镜面反射后得到A', ...