一步步实现自己的ORM(四)

通过前3章文章,大致对ORM有一定的了解,但也存在效率低下(大量用了反射)和重复代码,今天我们要对ORM进行优化。

具体流程如下:

我们优化的第一个就是减少反射调用,我的思路是定义一个Mapping,把表名、字段名信息缓存起来,EntityMapping 表示实体类信息对应数据库中的table,MemberMapping表示实体类的属性对应数据库中的Column。

EntityMapping代码:

    class EntityMapping
    {
        /// <summary>
        /// 对应实体类类型
        /// </summary>
        public Type EntityType { get; internal set; }

        /// <summary>
        /// 表名
        /// </summary>
        public string TableName { get; set; }

        /// <summary>
        /// 实体属性
        /// </summary>
        public List<MemberMapping> Members;

        /// <summary>
        /// 主键
        /// </summary>
        public List<MemberMapping> PrimaryKey { get; set; }

    }

MemberMapping代码:

    class MemberMapping
    {
        /// <summary>
        /// 列名
        /// </summary>
        public string ColumnName { get; internal set; }

        /// <summary>
        /// 属性名
        /// </summary>
        public PropertyInfo Member { get; set; }

        /// <summary>
        /// 是否为主键
        /// </summary>
        public bool IsPrimaryKey { get; set; }

        /// <summary>
        /// 是否为数据库自动生成
        /// </summary>
        public bool IsDbGenerated { get; set; }
    }

有Mapping后,我们现在要做的是把实体类里的信息存到Mapping类中,我在这里定义了一个AttributeMapping,里面方法如下:

    class AttributeMapping
    {
        private static Dictionary<Type, EntityMapping> entityMappings = new Dictionary<Type, EntityMapping>();

        public static EntityMapping Get<T>()

        public static EntityMapping Get(Type type)

        private static EntityMapping CreateMapping(Type type)
    }

其中Get方法就是根据实体类返回Mapping信息,而CreateMapping则是创建Mapping,具体代码见附件下载。

Mapping信息有了,我们需要做的是把重构之前的EntityHelper类,而这个类里有大量的链接数据库、操作数据库代码,我们把这些信息抽离到一个单独类,就好比我们常用的DbHelper类,而这个类我命名为DbProvider。

    class DbProvider
    {
        private IDbConnection conntion;
        public DbProvider(IDbConnection conntion)
        public virtual int ExecuteNonQuery(string sql, Dictionary<string, object> parameters)
        public virtual IDataReader ExecuteReader(string sql, Dictionary<string, object> parameters)
    }

EntityHelper类还有大量的SQL生成语句,我们为了简化EntityHelper类把SQL生成的代码放到单独的DbSqlBuilder类,为啥要这么做呢?除了简化代码外,还有就是为了适应不同的Db。

    class DbSqlBuilder
    {
        public string BuildInsertSql(EntityMapping entityMapping, List<string> columnNames)

        public string BuildUpdateSql(EntityMapping entityMapping, List<string> updateColumns,List<string> whereColumns)

        public string BuildDeleteSql(EntityMapping entityMapping, List<string> whereColumns)

     public string BuildSelectSql(EntityMapping entityMapping,string strWhere, string orderBy)
    }

为什么参数里要传递List<string>呢?因为我们拼SQL语句要用到列名的,修改后的EntityHelper(我在这里将名字改成DbContext)。

将数据库操作和SQL生成代码放到单独类里,回头再看下DbContext代码,比原来的简洁了很多。

    class DbContext
    {
        private DbProvider dbProvider;
        private DbSqlBuilder sqlBuilder ;

        public DbContext(string connectionString)
        {
            SqlConnection conn = new SqlConnection(connectionString);
            dbProvider = new DbProvider(conn);
            this.sqlBuilder = new DbSqlBuilder();
        }

        public int Insert<T>(object entity)
        {
            var entityMapping = AttributeMapping.Get<T>();

            //将Entity转换成Dictionary
            var parameters = DynamicMethodBuilder.ConvertFromObject(entity);
            var sql = sqlBuilder.BuildInsertSql(entityMapping, parameters.Keys.ToList());
            return dbProvider.ExecuteNonQuery(sql, parameters);
        }

        public int Update<T>(T entity)
        {
            var entityMapping = AttributeMapping.Get<T>();
            //将Entity转换成Dictionary
            var parameters = DynamicMethodBuilder.ConvertFromObject(entity);
            var columns = entityMapping.Members.Where(m => m.IsDbGenerated == false && m.IsPrimaryKey == false).Select(c => c.ColumnName).ToArray();
            var updateColumns = new Dictionary<string, object>();
            var whereColumns = new Dictionary<string, object>();

            foreach (var item in parameters)
            {
                if (columns.Contains(item.Key))
                    updateColumns.Add(item.Key, item.Value);
                if (entityMapping.PrimaryKey.All(m => m.ColumnName == item.Key))
                    whereColumns.Add(item.Key, item.Value);
            }

            var sql = sqlBuilder.BuildUpdateSql(entityMapping, updateColumns.Keys.ToList(), whereColumns.Keys.ToList());

            return dbProvider.ExecuteNonQuery(sql, parameters);
        }

        public int DeleteByKey<T>(params object[] values)
        {
            var entityMapping = AttributeMapping.Get<T>();

            if (values.Length != entityMapping.PrimaryKey.Count)
                throw new ArgumentException("参数个数和主键数不一致");

            var parameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            for (int i = 0; i < entityMapping.PrimaryKey.Count; i++)
            {
                parameters.Add(entityMapping.PrimaryKey[i].ColumnName, values[i]);
            }

            var sql = sqlBuilder.BuildDeleteSql(entityMapping, parameters.Keys.ToList());

            return dbProvider.ExecuteNonQuery(sql, parameters);
        }

        public T Get<T>(object[] values)
        {
            var entityMapping = AttributeMapping.Get<T>();

            if (values.Length != entityMapping.PrimaryKey.Count)
                throw new ArgumentException("参数个数和主键数不一致");

            var parameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

            StringBuilder where = new StringBuilder();

            for (int i = 0; i < values.Length; i++)
            {
                if (i > 0) //考虑到有多个主键
                    where.Append(" AND ");

                where.Append(entityMapping.PrimaryKey[i].ColumnName).Append("=").Append("@p").Append(i);

                /*参数*/
                parameters.Add("@p" + i, values[i]);
            }

            var sql = this.sqlBuilder.BuildSelectSql(entityMapping, where.ToString(), string.Empty);

            return GetEntityList<T>(sql, parameters).FirstOrDefault();
        }

        public List<T> GetList<T>(string where, string orderBy)
        {
            var entityMapping = AttributeMapping.Get<T>();

            var sql = this.sqlBuilder.BuildSelectSql(entityMapping, where.ToString(), orderBy);

            return GetEntityList<T>(sql, null);
        }

        public List<T> GetEntityList<T>(string sql, Dictionary<string, object> parameters)
        {

            var reader = dbProvider.ExecuteReader(sql, parameters);
            List<T> list = new List<T>();
            var type = typeof(T);
            var properties = type.GetProperties();
            while (reader.Read())
            {
                var user = Activator.CreateInstance(type);
                for (int i = 0; i < properties.Length; i++)
                {
                    var pi = properties[i];
                    if (reader[pi.Name] != null) //等同于 if (reader["UserId"] != null)这样的语句
                        pi.SetValue(user, reader[pi.Name], null); //等同于 user.UserId = (int)reader["UserId"];
                }

                list.Add((T)user);
            }
            return list;
        }
    }

本章结束,下一章要将DataReader转换成实体类的代码优化。

下载地址:http://files.cnblogs.com/files/sobaby/ORM04.zip

时间: 2024-10-21 17:53:29

一步步实现自己的ORM(四)的相关文章

一步步实现自己的ORM(二)

在第一篇<一步步实现自己的ORM(一)>里,我们用反射获取类名.属性和值,我们用这些信息开发了简单的INSERT方法,在上一篇文章里我们提到主键为什么没有设置成自增长类型,单单从属性里我们无法识别哪个是主键,今天我们用Attribute来标识列,关于Attribute,引用MSDN里描述      MADN的定义为:公共语言运行时允许添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型.字段.方法和属性等.Attributes和Microsoft .NET F

一步步学习Hibernate框架(四):采用jpa实现一对多关联映射(二)

上篇博客已经采用jpa注解来实现了一对多关联映射,将这种关联映射以外键的形式处理,现在来看看怎么以第三张表的形式处理.采用jpa注解来映射数据库要使用的jar包见上篇博客: 一步步学习Hibernate框架(三):采用jpa实现一对多关联映射(一) 现在看第二种方式:以第三张表的关系来体现 Group.java package com.tgb.zhudan; import java.util.List; import javax.persistence.CascadeType; import j

一步步实现自己的ORM(五)

上一张优化了ORM的INSERT.UPDATE.DELETE,但将数据库里的值填充到实体类这块还没优化.另外有博友在网上咨询说你这个都是查询所有字段的,而他的需求是按需查询字段,不是一次性取出来所有字段的,在这里我请这位朋友耐心等待,这个会在后面章节提到的.这次我们先优化datareader->entity,将数据库里的值Mapping到实体类里,我们常用的有两种办法第一种是采用EMIT方式,第二种是采用Expression tree表达式,这两种方案在性能上差异不大,但写法上第二种较第一种比较

Django基础(一)

h1,h2,h3 { display: block } h1 { background-color: #e59373; text-align: center; color: black !important } h2 { background-color: #8aab30; width: 60% } h3 { background-color: #336699; color: white; width: 30% } 知识预览 一 Django基本命令 二 路由配置系统(URLconf) 三 编写

[转发]confluence wiki 安装破解汉化

说明:此文在confluence-wiki-5.6.5版本亲测通过 (包括了wiki的程序以及破解汉化需要的所有文件) 一.解压zip文件之后的 附件文件 1.atlassian-confluence-5.6.5.tar.gz    confluence wiki官方程序 2.mysql-connector-java-5.1.25-bin.jar    如果使用mysql需要此驱动 3.confluence5.x-crack.zip    破解工具 4.atlassian-extras-2.4.

不要宅要养生--程序员健康生活指北

改不完的需求,加不完的班,修不完的BUG,熬不完的夜,程序猿如何拒绝疲劳,改善颈椎腰椎的巨大压力,上嵌美女老师跟你聊聊程序猿的健康生活的七招. 一.多站少坐.站立式工作台,升降桌,可以让你有更多时间站着办公,coding: 二.主动多走动.如站起来接打电话,接水时多走几步,定时站起来活动一下: 三.学会快速的(15分钟)锻炼方式.如yoga,提踵等,听着音频跟老师一步步来练习一下: 四.按时吃早餐,多喝水,每天摄入新鲜的水果和蔬菜: 五.饮食清淡,拒绝暴饮暴食,细心的老师把食谱都给准备好了: 六

confluence5.6安装

转自:http://ju.outofmemory.cn/entry/157013 说明:此文在confluence-wiki-5.6.5版本亲测通过附件:http://pan.baidu.com/s/1sjulMBr (包括了wiki的程序以及破解汉化需要的所有文件) 一.附件文件说明 1.atlassian-confluence-5.6.5.tar.gz    confluence wiki官方程序 2.mysql-connector-java-5.1.25-bin.jar    如果使用my

Python 如何debug

一.常见错误: 1.漏了末尾的冒号,如 if语句,循环语句,定义函数 2.缩进错误,该缩进的时候没有缩进 3.把英文符号写成中文符号,如:  ' ' () , 4.字符串拼接,把字符串和数字拼接一起 5.没有定义变量 6.“==”与“=”混用 7.缩进Tab键与空格混用 二.知识不熟练造成bug: 如:1.列表的索引是从0开始的,不是从1开始的: 2.append()函数每次只能添加一个参数,如a.append('A') 三.思路不清晰造成bug: 方法:1.用 # 把感觉会出问题的代码段注释掉

使用自定义View

1 关于自定义的View的构造函数 java中对构造函数只是调用,不继承.因为整个UI是android系统提供的框架,因此构造函数需要写成它要求的格式,即和View的构造函数一样. 自定义的View中要自己实现View同参构造函数,因为一般View都会在xml中定义,这样的构造函数会被sdk调用. 有以下几点结论: 第一,在代码中直接new一个Custom View实例的时候,会调用第一个构造函数: 第二,在xml布局文件中调用Custom View的时候,会调用第二个构造函数: 第三,在xml