• never下的easysql


    什么是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>
    View Code

    这样可以确保根据参数动态构造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>
    View Code

    这样下面的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>
    View Code

     我们来看看其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>
    View Code

    可以知道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;
            }
    View Code

    分析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
    }
    View Code

    原来是我们刚才上面说的到“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; }
        }
    View Code

    则对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;
            }
    View Code

    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
  • 相关阅读:
    30条MySQL优化总结
    安装了最新的pycharm不能自动补全的解决方法。路径中输入变成¥的解决方法
    You should consider upgrading via the 'python -m pip install --upgrade pip' command解决方法
    B站MySQL学习之笔记
    B站MySQL学习之job_grades表数据
    B站MySQL学习之student数据
    Faker 造相对真实的测试数据
    浏览器地址栏输入url后的过程
    cookie、session及token的区别
    Get和Post的区别
  • 原文地址:https://www.cnblogs.com/shelldudu/p/11010817.html
Copyright © 2020-2023  润新知