zoukankan      html  css  js  c++  java
  • 实现简单的ORM

    介绍

    本篇将介绍实现简单的ORM,即:对数据表的通用操作:增、删、改、查

    数据访问层

    数据访问层类图

    类说明:

    1.DbProvider(供应):为数据操作提供基本对象,如:连接、操作对象、事务。。。

    2.DbContext(环境):执行数据操作,如:返回DataReader、执行单条语句、执行事务。。。

    3.SqlContext(SQLServer环境):针对不同数据库的操作环境。(本例为针对SQLServer)

    代码说明:

    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对象在事务方法中创建,执行语句由外部传入

    所以:该方法的签名为一个Func<DbCommand,int>的委托,调用者可以直接获取到DbCommand对象执行语句,而真正的DbCommand对象统一由事务方法创建

    数据表字段映射层

    表映射代码:

    /// <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

    本篇就介绍到这里,感谢大家的耐心阅读。

    下一篇将介绍:反射的优化

    源码下载

  • 相关阅读:
    element-ui 设置input的只读或禁用
    vue 获取后端数据打印结果undefined问题
    用yaml来编写配置文件
    [LeetCode] 28. 实现strStr()
    [LeetCode] 25. k个一组翻转链表
    [LeetCode] 26. 删除排序数组中的重复项
    [LeetCode] 24. 两两交换链表中的节点
    [LeetCode] 23. 合并K个排序链表
    [LeetCode] 21. 合并两个有序链表
    [LeetCode] 22. 括号生成
  • 原文地址:https://www.cnblogs.com/color-wolf/p/6784800.html
Copyright © 2011-2022 走看看