在咱們早期寫的代碼中,想實現組裝靈活的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>
這樣能夠確保根據參數動態構造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>
這樣下面的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語法的定義)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>
能夠知道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種參數控制標籤
<ifnull parameter="UserName" then="and"> UserName = @UserName </ifnull>
UserName會傳入一個DbNull.Value對象,此時還不如使用ifcontain標籤
4種流程與內容標籤
加載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 其它
文章導航: