本篇將介紹實現簡單的ORM,即:對數據表的通用操做:增、刪、改、查sql
數據訪問層類圖數據庫
類說明:數組
1.DbProvider(供應):爲數據操做提供基本對象,如:鏈接、操做對象、事務。。。app
2.DbContext(環境):執行數據操做,如:返回DataReader、執行單條語句、執行事務。。。框架
3.SqlContext(SQLServer環境):針對不一樣數據庫的操做環境。(本例爲針對SQLServer)ide
代碼說明:測試
public int ExecuteNonQuery(Func<DbCommand, int> tranExecuteSQL) { using (DbConnection conn = m_Provider.CreateConnection(m_ConnString)) { DbTransaction tran = m_Provider.CreateTransaction(conn); using (DbCommand cmd = m_Provider.CreateCommand(conn)) { int num; m_Provider.PrepareCommand(conn, cmd, tran); try { num = tranExecuteSQL(cmd); tran.Commit(); } catch { tran.Rollback(); throw; } return num; } } }
這裏主要說明事務實現的代碼(其他代碼能夠在文章末尾下載源碼):要想保證事務的特性,就必須保證事務中的語句用的是同一個DbCommand對象
優化
要想保證事務中的全部語句使用的是同一個DbCommand對象,最簡單的方法就是:DbCommand對象在事務方法中建立,執行語句由外部傳入ui
因此:該方法的簽名爲一個Func<DbCommand,int>的委託,調用者能夠直接獲取到DbCommand對象執行語句,而真正的DbCommand對象統一由事務方法建立this
表映射代碼:
/// <summary> /// 表特性 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)] public class TableAttribute : Attribute { /// <summary> /// 表名 /// </summary> public string TableName { get; set; } /// <summary> /// 前綴(列) /// </summary> public string Prefix { get; set; } public TableAttribute(string name = "", string prefix = "") { if(!string.IsNullOrWhiteSpace(name)) this.TableName = name; if(!string.IsNullOrWhiteSpace(prefix)) this.Prefix = prefix; } }
列映射代碼:
/// <summary> /// 行特性 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)] public class ColumnAttribute : Attribute { /// <summary> /// 列明 /// </summary> public string ColumnName { get; set; } /// <summary> /// 是不是主鍵 /// </summary> public bool IsPrimaryKey { get; set; } /// <summary> /// 是不是自增列 /// </summary> public bool IsIdentity { get; set; } public ColumnAttribute() { } public ColumnAttribute(string columnName) { ColumnName = columnName; } }
代碼很簡單,且有註釋,這裏就不復述了
實體映射應該算ORM框架的核心,分解實體層要作的事,而後逐步講解代碼實現
1.實體屬性-表字段的映射關係
代碼:
/// <summary> /// 獲得類型全部實例屬性(含特性) /// </summary> /// <typeparam name="T">類型</typeparam> /// <returns>鍵值對:Key:表特性;值:屬性名-列特性</returns> public static KeyValuePair<TableAttribute, Dictionary<string, ColumnAttribute>> GetPropertiesForModel<T>() where T : new() { KeyValuePair<TableAttribute, Dictionary<string, ColumnAttribute>> KVProperties; Type type = typeof(T); //表 TableAttribute tableAttr = type.GetCustomAttribute<TableAttribute>(); if (tableAttr == null) tableAttr = new TableAttribute(name: type.Name); KVProperties = new KeyValuePair<TableAttribute, Dictionary<string, ColumnAttribute>>(tableAttr, new Dictionary<string, ColumnAttribute> { }); //屬性 PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo proInfo in properties) { //類直接過濾 if (!proInfo.GetType().IsClass) continue; ColumnAttribute attr = proInfo.GetCustomAttribute<ColumnAttribute>(); KVProperties.Value.Add(proInfo.Name, attr ?? new ColumnAttribute(proInfo.Name)); } return KVProperties; }
代碼很簡單,這裏就再也不復述了
但有一點要說明:當屬性上沒有使用列特性時,代表列名與屬性名稱一致,因此列特性中存放其列名稱(即:屬性名稱)。這樣作的目的是方便後面組織sql語句
2.根據不一樣的數據庫操做命令(INSERT、DELETE、UPDATE、SELECT),組織生成對應的SQL語句。這裏重點介紹一下組織SQL參數的方法
代碼:
/// <summary> /// 生成SQL語句 /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="model">實體</param> /// <param name="operate">操做類型</param> /// <param name="operateSql">條件語句</param> /// <returns>Item1:操做sql語句;Item2:參數數組;Item3:列名-屬性名</returns> internal static Tuple<string, DbParameter[], Dictionary<string, string>> OperateSQL(T model, Operate operate, string operateSql) { var modelInfo = GetCache(model); Dictionary<string, ColumnAttribute> pmc = GetPropertyMappColumn(modelInfo.Key.Prefix, modelInfo.Value); StringBuilder sql = new StringBuilder(100); LinkedList<DbParameter> parms = new LinkedList<DbParameter>(); //操做表語句(表名不能夠經過@參數形式傳遞,因此只能採用拼接) sql.AppendFormat(operateSql, modelInfo.Key.TableName); if (model != null) { switch (operate) { case Operate.SELECT: SelectSQL(model, pmc, sql, parms); break; case Operate.INSERT: InsertSQL(model, pmc, sql, parms); break; case Operate.UPDATE: UpdateSQL(model, pmc, sql, parms); break; case Operate.DELETE: DeleteSQL(model, pmc, sql, parms); break; } } return new Tuple<string, DbParameter[], Dictionary<string, string>>(sql.ToString(), parms.ToArray(), pmc.ToDictionary(c => c.Key, c => c.Value == null ? c.Key : c.Value.ColumnName)); }
介紹:
a.根據不一樣的數據庫操做命令,調用生成不一樣的SQL語句的方法b.返回:生成的SQL語句,數據庫參數數組,實體屬性名-數據表字段名的對應關係
該方法只是提供給外部調用的方法,真正處理SQL參數語句的方法是下面這個方法
代碼:
/// <summary> /// 準備參數語句 /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="model">實體類</param> /// <param name="pmc">屬性-列特性</param> /// <param name="condFormat">條件格式</param> /// <param name="parms">sql參數</param> /// <returns>Item1:有值得屬性名稱;Item2:條件</returns> private static Tuple<LinkedList<string>, LinkedList<string>> ProvieParameter(T model, Dictionary<string, ColumnAttribute> pmc, Func<string, string, string> parmFormat, LinkedList<DbParameter> parms) { //屬性集合 LinkedList<string> propertyNames = new LinkedList<string>(); //條件集合 LinkedList<string> conditions = new LinkedList<string>(); foreach (string key in pmc.Keys) { //屬性值 object propertyValue = model.GetType().GetProperty(key).GetValue(model); if (propertyValue == null) continue; propertyNames.AddLast(key); //條件 if (condFormat != null) conditions.AddLast(condFormat(pmc[key].ColumnName, key)); //參數 parms.AddLast(SqlContext.GetInstance().CreateParameter(string.Format("@{0}", key), propertyValue)); } return new Tuple<LinkedList<string>, LinkedList<string>>(propertyNames, conditions); }
介紹:
a.處理三個集合:屬性集合、條件集合、參數集合
b.根據傳入的屬性名-列特性(pmc)參數:將屬性提取出來保存入屬性集合
c.根據傳入的SQL條件格式(Func<列名稱,參數名稱,組合生成條件SQL語句> condFormat)參數:生成條件集合。如:@參數名,@參數名...(INSERT語句);字段名=@參數名,字段名=@參數名...(SELECT與DELETE語句)
d.根據傳入的實體(屬性與屬性值):填充SQL參數集合
代碼:
public static int Insert<T>(T model) where T : new() { if (model == null) throw new ArgumentNullException(model.ToString()); //sql語句、sql參數 Tuple<string, DbParameter[], Dictionary<string, string>> sqlParameter = EFToSQL<T>.OperateSQL(model, Operate.INSERT, "INSERT INTO {0}"); return SqlContext.GetInstance().ExecuteNonQuery(sqlParameter.Item1, sqlParameter.Item2); }
這裏只貼出了INSET方法,其他方法可下載源碼查看
代碼:
static void Main(string[] args) { IList<User> users = EFContext.Select<User>(null); if (users != null) { foreach (User user in users) { Console.WriteLine("用戶名:{0},GUID:{1}", user.UserName, user.RecordId); } } }
本篇就介紹到這裏,感謝你們的耐心閱讀。
下一篇將介紹:反射的優化