實現簡單的ORM

介紹

本篇將介紹實現簡單的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;
        }
    }
}
View Code

這裏主要說明事務實現的代碼(其他代碼能夠在文章末尾下載源碼):要想保證事務的特性,就必須保證事務中的語句用的是同一個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;
    }
}
View Code

列映射代碼:

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

代碼很簡單,且有註釋,這裏就不復述了

實體映射層

實體映射應該算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;
}
View Code

代碼很簡單,這裏就再也不復述了

但有一點要說明:當屬性上沒有使用列特性時,代表列名與屬性名稱一致,因此列特性中存放其列名稱(即:屬性名稱)。這樣作的目的是方便後面組織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));
}
View Code

介紹:

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

介紹:

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

這裏只貼出了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);
        }
    }

}
View Code

本篇就介紹到這裏,感謝你們的耐心閱讀。

下一篇將介紹:反射的優化

源碼下載

相關文章
相關標籤/搜索