never下的easysql

什麼是EasySql

在咱們早期寫的代碼中,想實現組裝靈活的sql語句與參數,咱們能夠去翻閱早期本身寫的代碼html

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

因此我想要一種能夠根據參數去動態構造sql語句的項目。有人推薦IBatisNet,使用的1年內,我想不少人也會出現這樣的代碼:shell

  <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標籤session

  <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語法的定義)app

<?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
相關文章
相關標籤/搜索