Step by Step-构建本身的ORM系列-数据访问层

1、开篇

         距离上篇《Step by Step-构建本身的ORM系列-开篇》的时间间隔的过久了,很对不住你们啊,主要是由于有几个系列必须提早先写完,才能继续这个系列,固然我也在html

写这几个系列的过程当中,对ORM这个系列中的原来的实现的想法有了新的认识和改进,固然这些都不是说是很先进的思想或者认识,也多是你们见过的思路吧,但愿后面我能在面试

写设计模式系列的过程当中,穿插讲解ORM系列,固然个人这个构建的系列,也只能说是很简易的,本身平时开发个小应用工具或者什么的,可能用他,由于是本身开发的嘛,毕竟sql

使用起来仍是比较顺手的!符合本身的操做习惯嘛。数据库

        固然我写这个系列的过程当中,也会有本身认识偏激的地方,或者思路不正确的地方,还请大伙多多指出和批评。我也是在我目前的项目中学习到了不少的宝贵的经验,其实设计模式

咱们应该能看到ORM给咱们提供的方便和不便之处,咱们取其精华,剔除糟粕,不过这真的很难。我其实对一些流行的ORM的底层实现,研究的很少也不深,像Nhibernate,我缓存

只是了解Hibernate,是当时从JAVA中了解过来的,不深刻,Castle框架却是用过一段时间,EntityFreamWork,我也没有用过,只是象征性的下载最新版本,体验了下AOP的服务器

方式,我感受其实有不少的时候,咱们使用AOP的方式,可以改进咱们程序的灵活性。这块可能还须要大牛们多多指点。架构

        我理想的ORM是实现持久化的透明,这个怎么理解呢?就是说我在程序的开发中,我不想在业务代码中书写相应的持久化操做,也不关心业务层中的去如何调用你的并发

ORM,去完成CRUD的操做。我只关心个人业务逻辑,这个有点像DDD(领域驱动开发)里面的领域层了,只关心领域内部的业务逻辑,而不关心其余的东西,这样方便咱们快速框架

的抓住关注的东西,而尽可能让与领域无关的东西不要影响业务领域逻辑的实现。

2、摘要

        本篇主要开始讲述《Step by Step-构建本身的ORM系列-数据访问层》关于数据访问层的部分,其实前面也对这块的内容有了必定的介绍了,其实本篇就是教你如何完成

ORM中的数据访问层的操做,这里是提供统一的数据访问方法的实现。固然这里的操做仍是主要集中在数据库的操做,包括如何根据实体对象返回实体的列表,包括生成SQL语

句的几类实现,还包括一些实现后续的ORM的配置管理的维护,这里就是提供可视化的XML文件的配置,这个具体怎么来作呢?由于咱们平时针对ORM的使用,都是直接修改

XML文件,咱们能够提供一个可视化的界面,让那个用户配置这些相应的设置。经过这些配置,咱们能够实现数据库的平滑的迁移,缓存应用的配置,包括其余的一些相关设置信

息。整体来讲这些操做均可以依托于,咱们这里的数据访问层来完成。

        咱们来看看ORM中数据访问层的重要做用和地位吧:

image上图咱们知道,咱们全部的相关功能的基础,都是基于数据访问层来作的,因此咱们处理好这个层的相关逻辑

后,后续的问题就会比较容易开展。下面咱们就会针对这些疑问开始

一个个的解决咱们的数据访问层应该提供的相关功能!大致的功能应该有以下功能:

一、持久化的操做方法CUD。能够扩展提供建立表+其余的修改表等相关的自动脚本工具。提供持久化透明的方式。

二、提供缓存服务,将对象的相应映射信息缓存起来,这样后续执行生成语句等操做,效率上会是很大的提高。

三、咱们在处理对象对于Update语句,应该能处理好只能更变化的信息,若是没有发生变化,那么咱们是否是不用执行更新操做了呢?减小数据库的操做次数。

四、提供基础的查询方法,之后全部的基于ORM上的查询基于这个查询进行扩展。提供持久化透明的查询方式。

五、并发和事务的控制。咱们这里可能提供一个内部的版本号的方式来作,一旦修改过这个对象或者发生改变,任什么时候候的操做,咱们都是针对这个版本号的记录来作的,版本号

经过内部提供的方法来进行。

3、本文大纲

       一、开篇。

       二、摘要。

       三、本文大纲。

       四、ORM之数据访问层分析。

       五、ORM相关代码实现。

       六、本章总结。

       七、系列进度。

       八、下篇预告。

4、ORM之数据访问层分析

        咱们先来针对上面的几个问题,咱们给出实现思路,来分析下给出的思路的可行性和如何实现的解析。具体的代码下节给出核心实现方案。

       4.一、提供通用的持久化的操做

        这个具体的解析在上篇中已经给出了相应的思路了,咱们经过在底层提供相应的方法来作。通常来讲,对应数据库的四种操做,咱们在数据访问层,也提供了相应的语句的

自动构造的过程,具体的构造,咱们前面给出的实现方案是经过特性来实现,特性中定义具体的数据库字段,类型,长度等一些列的参数。我这里就不复述了,我这里分析下咱们

这样实现的好处。咱们知道继承的方式是挺好的,我为何这么说,经过提供一个基类,基类中定义通用的CUD的操做方法,这样只要是继承这个类的子类,都会有CUD的操做

方法了,可是咱们为了提供持久化透明的方案,那么无疑,对于持久化的操做,咱们就不但愿由业务逻辑层中的业务逻辑对象来完成,那么如何来作呢?咱们经过数据访问层,提

供统一的操做方法,让服务层来完成业务对象的持久化操做。这样就能实现,持久化透明的方案。

       因此咱们能够这样来作,在数据访问层中,咱们提供一个接口,接口中定义持久化操做的几类方案,经过不一样的实现配置,咱们能够在XML配置文件中进行指定,咱们采用

重量级的ORM仍是轻量级的ORM,这样咱们也理想的实现了低耦合的特性。

       同时,对于不一样的文件的操做,咱们能够支持多文件类型的写入,固然对于不一样的数据库存储,若是咱们利用关系型数据库咱们须要ORM,对于对象数据库的操做,或者

XML文件的操做,咱们这时候的ORM就变了。

       4.二、提供缓存服务

         咱们知道,咱们第一篇中主要是经过自定义特性+反射的形式来处理:咱们并无提供完整的特性操做,其实还有不少的状况,好比说,咱们还能够提供对视图的映射,提

供一个视图的特性等。还有其余的特性有不少,后续会给出完整的代码结构,咱们知道自定义特性+反射,若是每次在将对象映射成数据库表的时候,那么效率上是多么的低下

啊,那么这个时候,咱们能够考虑使用缓存的方式,将数据库表中的数据库列与对象中的属性列进行映射,咱们能够把这些对应关系放在缓存中,那么若是咱们在后续的处理中遇

到与数据库相关操做,须要进行对象映射的时候,咱们都先会去缓存中查找有没有指定键值的映射数据库表列存在,存在取出生成SQL语句,不然经过反射取出对应的数据库表

列,放在缓存中。下面给出示意图。将这个过程进行描述:

image固然我这里给出的确定是反向的根据对象生成操做数据的SQL语句的方式。这里没有考虑,当数

据库表发生变化的时候,我应该自动同步缓存中的映射集合信息,固然咱们能够经过必定的策略,来实现这样的双向同步问题。例如以下的方式可能就是可行的方案。

image,经过上述的方式,我

们经过同步组件,在每次进行数据操做以前,咱们能够应用更好的策略,好比记录或者遍历文件的修改状态,对比文件的最后修改日期,是否是发生修改,或者当某个文件发生修

改以后,咱们记录在某个配置文件中,这样咱们能够提升同步的效率,由于经过这样的方式,咱们不须要每次检查对象是否是发生变化了,这样咱们若是发现对象没有发生变化,

那么咱们就不要让同步组件去检测对象是否发生变化,这样就能提升效率,同时支持当映射对象发生变化的时候,咱们不用修改咱们的关系数据库。你们都知道,面向对象设计建

模与关系数据库的最大难题就是双方的变化的同步性的方案是很难定的,我这里只是给出一个简单的思路和方式,可能还有更好的方案,也请你们多多告诉我。

      4.三、Update语句的操做

       我不知道,大家在面试的时候,若是你常常开发底层的面向对象的ORM的时候,应该会遇到这样的问题,咱们在查询一个映射对象的时候,咱们可能只须要取出这个对象的

部分列,而不是所有的数据列,这个时候,咱们如何指定呢?就是填充对象的时候,咱们只须要填充指定的列?或者咱们须要在在保存编辑的时候,咱们不更新未发生变化的数据

库,其实主要是要求,咱们在生成SQL语句的时候,咱们但愿咱们的更新语句中不要出现,没有发生变化的数据列的设置,这个如何作到呢?我想咱们能够经过以下的2种方式来

作。

        一、经过对象的序列化,来复制一个对象,而且系统中缓存这个对象,在编辑以前,缓存,等到提交后,释放这个对象。这个由系统默认的提供方法。前提是标记对象是编

辑状态,这样在修改对象以前进行复制,否则若是修改完了,再复制就没有什么意义了

        二、经过字典来保存更新数值的数据列,经过数据字典来存放。咱们在字典中存放映射的数据列,将发生变化的数据列和数据列的值进行标记发生改变,咱们在生成更新语

句的时候。直接遍历这个集合,将列状态发生改变的列生成相应的操做语句便可。这些都是可行的方式,咱们在前面的架构设计中也提到过的。都是给过思路的,这里我也很少复

述了。

        三、…。可能还有其余的更好的方式,还请你们多提出好的思路,我备注在这个位置!

       4.四、提供基础的查询方法。

        这里说的提供基础的查询方法,指的是基于数据库操做之上,咱们提供几个经常使用的查询方法,包括自动生成版本号的方法等,咱们的版本号能够经过日期+流水号的形式来

生成,或者是其余的状况。GUID也是可行的办法。不过维护起来可能不是很方便。因此底层提供相应的操做方法更容易来作。

        咱们这里考虑提供以下的基础查询方法,复杂的查询方法,咱们能够提供一个入口来作。例如咱们提供以下几类方法:

        一、底层生成版本号的方法,自增ID流水号,根据不一样的生成规则自定义设置ID生成规则,来组织生成ID的通用方法。

        二、提供实体与数据库行集之间的转换,咱们须要将数据库记录转换为实体集合。经过查询方法返回对象集合。这里提供返回指定主键的对象集合。

        三、返回一个数据表或者视图中的全部记录。

        四、返回传入分页个数,和分页排序字段,分页条件的分页集合。

        五、返回指定列的查询方法。(这里没有想到好的办法,怎么样的形式比较灵活可以动态的指定返回的列,好比说1列,10列,5列等),但愿你们提出好的意见和建议!

        六、提供统一的入口,编写SQL语句传入到数据访问层中进行查询和检索,根据指定的返回类型来返回泛型对象。

        4.五、并发和事务控制

         我想一个系统中必须考虑的就是事务处理了,咱们进行批量操做的时候,若是数据不一样步,那就太痛苦了,也是不能使用的系统的,咱们但愿咱们的ORM可以自动的集成

事务和并发,固然这里说的并发是当用户数上升到必定量的时候,就会产生这样的问题,理论上来讲只要有2个以上的用户,就必须考虑并发操做!并发咱们有几个控制的思路,

整体来讲应该说说咱们前面的设计的内部的一个自动生成的版本号,是最好的选择。具体怎么个意思呢?咱们来解释下:

         对于并发控制,咱们知道,并发控制的问题:写丢失,读出来的数据是脏数据,无疑就是这么2个比较常见的问题,那么咱们如何来对写丢失进行限制呢?目前通用的方案

都是经过乐观锁来处理,二我的能够同时对某个信息进行读取,可是只能有一我的在进行编辑,可是最后修改的内容会把前面修改的信息覆盖掉,这是乐观锁的处理方式。

        悲观锁,则是只要有人在修改,那么可能你不能进行修改,也不能读取,这种方式,固然能够保证信息的修改的同步性和一致性,可是用户的易用性和友好性方面不够人性

化,相比来讲,有人修改,就不能被其余人修改,可是能够读取的方式体验方面要差一些,不过各有使用的场景,通常来讲,悲观锁是乐观锁的一个补充。

        咱们这里既不是乐观锁,也不是悲观锁的形式,经过版原本对某个记录的版本进行记录,一旦发生改变,那么记录的版本就要发生变化,咱们这里对这个行集的版本的更新

能够经过ORM提供的版本的生成规则来生成一个版本号,或者是经过触发器来实现,固然性能也是咱们须要考虑的部分。

        对于事务,我想通常的不是分布式操做的应用,咱们经过数据库提供的自己的事务服务来完成,基本上就能够知足平常的需求,也没有什么特别难的地方,我想这里我也就

不详细的说了,咱们来简单的说下,分布式事务的一致性,对于这种分布式的事务操做,咱们能够采用离线并发模式来处理。这个怎么理解呢?就是经过工做单元来实现。咱们把

每个操做看着一个工做单元,若是咱们在执行某个事务操做的过程当中,若是返回是0或者是其余的不是咱们指望的结果时,咱们不会进行任何的提交操做,若是所有执行经过,

咱们循环全部的工做单元进行提交,不然咱们回滚全部的系统事务。咱们把这样的分布式事务,看做一个业务事务,由一些列的工做单元组成,这些工做单元看做是系统事务。

5、ORM相关代码实现

         5.一、CUD的基本实现代码:

           1,Create语句的实现:

        private Dictionary<string, Column> _autoIncrementColumns = new Dictionary<string, Column>(); 
        private Dictionary<string, Column> _updateColumns = new Dictionary<string, Column>();

        public string TableName 
        { 
            get 
            { 
                return string.Empty; 
            } 
        }

        public Dictionary<string, Column> UpdateColumns 
        { 
            get 
            { 
                return this._updateColumns; 
            } 
            set 
            { 
                this._updateColumns = value; 
            } 
        }

        public Dictionary<string, Column> AutoIncrementColumns 
        { 
            get 
            { 
                return this._autoIncrementColumns; 
            } 
            set 
            { 
                this._autoIncrementColumns = value; 
            } 
        }

        public virtual IDbCommand GetDbCommand() 
        { 
            // 若是column的值没有被更新过,则返回null 
            if (this.UpdateColumns.Count == 0) 
            { 
                return null; 
            }

            ArrayList fieldList = new ArrayList(); 
            ArrayList valueList = new ArrayList(); 
            SqlCommand cmd = new SqlCommand();

            foreach (Column column in this.UpdateColumns.Values) 
            { 
                fieldList.Add("[" + column.Key + "]"); 
                valueList.Add("@" + column.Value); 
            }

            string fieldString = string.Join(" , ", (string[])fieldList.ToArray(typeof(string))); 
            string valueString = string.Join(" , ", (string[])valueList.ToArray(typeof(string))); 
            string cmdText = string.Format("INSERT INTO [{0}]({1}) VALUES({2})", 
                this.TableName, 
                fieldString, 
                valueString);

            string sqlGetIndentityID = null;

            if (this.AutoIncrementColumns.Count == 1) 
            { 
                sqlGetIndentityID = string.Format("SELECT [{0}] = SCOPE_IDENTITY()"); 
            }

            if (sqlGetIndentityID != null) 
            { 
                cmdText = cmdText + " ; " + sqlGetIndentityID; 
            }

            cmd.CommandText = cmdText;

            return cmd;

        } 
    }

 

下面给出Update语句,是从上面的更新集合中编辑,将列的状态发生改变的列添加生成到语句中-示例代码以下:

        public virtual IDbCommand GetDbCommand() 
        { 
            // 若是column的值没有被更新过,则返回null 
            if (this.UpdateColumns.Count == 0) 
            { 
                return null; 
            }

            ArrayList fieldList = new ArrayList(); 
            ArrayList valueList = new ArrayList(); 
            SqlCommand cmd = new SqlCommand();

            string updateSQL=string.Empty; 
            foreach (Column column in this.UpdateColumns.Values) 
            { 
                if (column.State) 
                    updateSQL += "[" + column.Key + "]=" + "@" + column.Value; 
            }

           string cmdText=  string.Format("UPDATE {0} SET {1}={2}", updateSQL);

            cmd.CommandText = cmdText;

            return cmd; 
        }

至于删除的代码比较简单,我这里就不给出删除的代码了,整体来讲形式是相同的。

             5.二、缓存服务代码

        我有2篇关于缓存的介绍,缓存中最难搞的问题就是缓存的过时的问题,对应反射的性能问题也是存在过时的问题,好比说咱们的数据库表发生变化,或者对象中的属性发

生变化后,那么咱们的缓存中的内容也须要进行更新,否则咱们生成的数据库操做语句将会不正确。咱们这里的策略就是将映射出来的对象,放在服务器中的缓存中,固然对于

B/S和C/S系统中可能采起的缓存方式和策略仍是有区别的。B/S咱们的缓存能够采用缓存到服务器中,或者是经过缓存服务器来完成,通常是经过Remoting来将服务器与缓存

服务器完成通讯。咱们看看简单的示例代码吧:

   public class Cache 
   { 
       private static System.Web.Caching.Cache cache = HttpRuntime.Cache;//这里是默认取当前应用程序的服务缓存。

       public static object Get(string key) 
       { 
           return cache[key]; 
       }

       public static bool Remove(string key) 
       { 
           return !(null == cache.Remove(key)); 
       }

       public static void Set(string key, object value) 
       { 
           cache.Insert(key, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(3)); 
       } 
   }

上面给出的缓存类的示例代码,具体的操做,使用反射后,将反射后对象元数据信息缓存起来,经过对象名来缓存:

具体代码以下:

           PropertyInfo[] property = null; 
            if (Cache.Get("") != null) 
            { 
                property = (PropertyInfo[])Cache.Get(""); 
            } 
            else 
            { 
                Type t = Type.GetType("");

                property = t.GetProperties(); 
            }

经过上面的几行简单的代码就能表达出咱们上面讲述的思路,具体如何过时,这个上面也给出了一些思路,可能大伙有更好的思路,我这里就不班门弄斧了。

      5.3,提供基础的查询服务

        我想大伙对于查询语句的操做,应该说是司空见惯了吧,咱们如何能更好的完成统一的查询服务多是咱们关心的问题,我这里不会给出多数据库的实现,可是能够给大伙

一个思路,咱们这里定义返回的查询命令的时候,若是说支持多数据的话,能够定义一个统一的接口,不一样的数据库提供不一样的实现接口,而后根据统一的ORM配置来调用不一样的

组件来生成SQL语句,完成调用操做。

        相关的查询服务代码以下:

       /// <summary> 
       /// 系统自动生成的版本号 
       /// </summary> 
       /// <returns></returns> 
       public string GetVersion() 
       { 
           return DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString(); 
       }

       public int GetMax<T>() 
       { 
           //根据T的类型获取T的最大列,完成查询操做。 
           string sqlText = " Select MAX(ISNULL(列名,0))+1 FROM TableName";

           return 0; 
       }

       public List<T> GetAll<T>() 
       { 
           //根据T的类型获取T的最大列,完成查询操做。 
           string sqlText = " Select *  FROM TableName";

           return new List<T>(); 
       }

       public List<T> GetList<T>(string condition,int pagesize,string orderField) 
       { 
           //根据T的类型获取T的最大列,完成查询操做。 
           string sqlText = " Select *  FROM TableName where " + condition + " order by " + orderField;

           return new List<T>(); 
       }

      上面给出的不是所有的代码,部分代码仍是你们本身去完成吧,我这里想的是,一些客户比较复杂的自定义代码经过一个接口传入的形式,来完成基础查询服务的调用。

      咱们这里给出通用的接口定义:

       CommandType CommandType 
        { 
            get; 
            set; 
        }

        string whereCondition 
        { 
            get; 
            set; 
        }

        string orderCondition 
        { 
            get; 
            set; 
        }

        string TableName 
        { 
            get; 
            set; 
        }

        Column[] ColumnList 
        { 
            get; 
            set; 
        }

        string SQL 
        { 
            get; 
            set; 
        }

                给出默认几类示例的实现:

    public class BaseSQL : ISelect 
    { 
        public System.Data.CommandType CommandType 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
            set 
            { 
                throw new NotImplementedException(); 
            } 
        }

        public string whereCondition 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
            set 
            { 
                throw new NotImplementedException(); 
            } 
        }

        public string orderCondition 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
            set 
            { 
                throw new NotImplementedException(); 
            } 
        }

        public string TableName 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
            set 
            { 
                throw new NotImplementedException(); 
            } 
        }

        public Column[] ColumnList 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
            set 
            { 
                throw new NotImplementedException(); 
            } 
        }

        public string SQL 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
            set 
            { 
                throw new NotImplementedException(); 
            } 
        }

        具体调用的代码以下:

   public class SpecialSQL : BaseSQL 
    { 
        public void Test() 
        { 
            this.TableName = ""; 
            this.SQL = " SELECT * FROM TEST "; 
            this.whereCondition = " ID=4 "; 
            this.orderCondition = " ORDER BY ID DESC "; 
        } 
    }

      固然这里的继承的方式不是很推荐,能够采用抽象工厂的模式,来建立这个查询对象,而后咱们在调用这个查询对象的地方,咱们能够自定义这个SQL查询对象,后台的

ORM自动解析,完成自定义SQL语句的统一查询服务入口。

      固然若是您有更好的方案,能够提出来,很是感谢!

6、本章总结

        本文主要是讲述ORM中的数据访问层,我这里因为一些特殊的缘由,代码给出的不是特别的详细,一方面是因为以前的那部分的代码丢了,如今一时难以还原,因此形成有

些代码给出的不是特别完整的状况,请你们见谅,文章中的有些部分的内容,我在实现的过程当中也是遇到了很多的问题,我如今的具体问题列出来,也请你们帮我解决一下,个人

疑问,我目前在架构设计的过程当中遇到以下的问题:

               一、我在实现服务层持久化透明服务的时候,我也想把查询服务透明,意思就是业务对象与服务层只经过DTO来完成,业务对象的全部数据都经过服务层的访问来完成。

               二、若是如今有比较复杂的业务逻辑的操做语句的时候,个人这个SQL语句放在数据访问层好呢?仍是放在哪里?应该具体的职责划分要明确。

               三、我只是但愿业务逻辑层处理业务数据,具体的业务数据怎么来的,我想让业务逻辑只关心DTO。

               四、对于这样的服务层提供的统一查询方式的话,我在表现层调用的时候,如何传参,可以很好的组织参数的传递和调用,传统的方式再也不目前的考虑当中。

               感谢大伙可以给出一些意见和建议!相信这个ORM系列能愈来愈好!

相关文章
相关标签/搜索