zoukankan      html  css  js  c++  java
  • 打造自己的数据访问层(续)ORM实施方案

    在前几篇打造自己的数据访问层中,已经实现了最基本的数据访问层,基于程序开发中ORM的火热程度,笔者一直在思考,我们的数据访问层是否也能引入ORM的概念,哪怕是最基础的实现。
    既然有想法,就该动手实施。
    ORM,说到底就是将对象实体与数据表之间建立映射关系。
    实施的话,首先得先提一提反射及自定义属性,试想一下,有如下模型,如何将其与数据表建立关联? 
    public class TestModel
    {
    public string ID
    {
    get;
    set;
    }

    public string NAME
    {
    get;
    set;
    }

    public string VAL
    {
    get;
    set;
    }
    }

    自定义属性在此时就派上用场了,定义表名、主键,应用于模型之上,通过反射获取对应信息。
    看看如何编写自定义属性,实质上只需继承Attribute即可,我们为属性标明表名、判断是否为主键,再判断是否为扩展(不需进行数据映射),就目前而言,有这些信息已足够了:
    public class ORMAttribute : Attribute
    {
    private string tableName = "";

    /// <summary>
    /// 是否为主键
    /// </summary>
    public bool IsKey = false;

    /// <summary>
    /// 是否为扩展信息
    /// </summary>
    public bool IsExtend = false;

    public ORMAttribute()
    {

    }

    public ORMAttribute(string tableName)
    {
    this.tableName = tableName;
    }

    public string GetTableName()
    {
    return tableName;
    }
    }

    应用于对象模型之上:
    [ORMAttribute("TEST")]
    public class TestModel
    {
    [ORMAttribute(IsKey
    = true)]
    public string ID
    {
    get;
    set;
    }

    public string NAME
    {
    get;
    set;
    }

    public string VAL
    {
    get;
    set;
    }
    }

    定义了属性,就得获取相应信息,这时反射来了了,通过Type来获取属性实例,并进一步获取对象信息,这是的关键方法是GetCustomAttributes,用于获取自定属性集:
    /// <summary>
    /// 从模型中获取数据表名
    /// </summary>
    public static string GetTableName(Type type)
    {
    ORMAttribute attr
    = null;
    object[] attributes = type.GetCustomAttributes(false);
    foreach (object o in attributes)
    {
    if (o.GetType() == typeof(ORMAttribute))
    {
    attr
    = (ORMAttribute)o;
    break;
    }
    }

    return attr.GetTableName();
    }

    /// <summary>
    /// 根据属性获取所有字段
    /// 返回字典值表示是否为主键
    /// </summary>
    public static Dictionary<string, bool> GetColumns(PropertyInfo[] pis)
    {
    Dictionary
    <string, bool> dictCols = new Dictionary<string, bool>();
    bool isKey = false;
    object[] attributes;
    ORMAttribute attribute
    = null;
    foreach (PropertyInfo pi in pis)
    {
    isKey
    = false;
    attribute
    = null;
    attributes
    = pi.GetCustomAttributes(false);
    foreach (object o in attributes)
    {
    if (o.GetType() == typeof(ORMAttribute))
    {
    attribute
    = (ORMAttribute)o;
    isKey
    = attribute.IsKey;
    break;
    }
    }

    if (attribute != null && attribute.IsExtend)
    {
    //丢弃扩展属性
    continue;
    }

    dictCols.Add(pi.Name, isKey);
    }

    return dictCols;
    }

    表名、字段、主键都已获取,映射的主体内容已完全获取,接下来思考的是如何与现有数据访问层进行关联。
    回过头来看看数据访问都做了些什么:
    1、获取实体访问数据库。
    2、建立内存表与物理表的之间映射关系。
    3、以DataSet为基础进行查询与更新操作。
    该做的都已经做了,如果能将对对像模型进行的更改操作与DataSet建立同步关联的话,那么也就能实现对数据库的操作了。
    接下来就该借助于自定集合了,具体方式请参考前篇:创建自定义集合,我们将实体模型保存于集合中,当集合进行增、删、改操作时,同步对DataSet进行修改。
    实现同步,得利用事件原理了,关于事件,本篇不详述。
    我们为集合定义三个事件:
    新增、修改、删除,当集合进行此三种操作时事件将被自动激活:
    public delegate void ORMEventHandler(object sender, ORMListEventArgs<T> e);
    public event ORMEventHandler AddEvent;
    public event ORMEventHandler RemoveEvent;
    public event ORMEventHandler UpdateEvent;
    激活事件,以更新方法为例:
    /// <summary>
    /// 更新
    /// </summary>
    public void Update(T model)
    {
    if (UpdateEvent != null)
    {
    ORMListEventArgs
    <T> e = new ORMListEventArgs<T>(model);
    UpdateEvent(
    this, e);
    }
    }

    集合准备好了,再来就是要建立映射关系了,如数同据访问层的映射对象一样,这里同样需要一个映射对象,只是这里是的映射并不是与数据库实体间时行关联,而仅仅是对原数据访问层映射的一种重复利用而已,看看它应该具备的功能:
    1、获取模型信息:表名、字段、主键。
    2、将模型填充于集合当中。
    3、对集合进行监听,当列表数据改变时,同步对DataSet进行修改。

    第1点已经实现了,利用自定义属性。
    看看第2点,填充对象集合,利用已有映射对象获取数据集,通过反射获取对象信息并进行赋值操作:
    /// <summary>
    /// 填充对象模型
    /// </summary>
    public void Fill(string sqlText)
    {
    //填充DataSet
    map.Fill(sqlText, tableName);
    dt
    = executeObj.GetDataSet().Tables[tableName];

    //填充对象模型
    string typeName = ORMHelper.GetTypeName(type) ;
    PropertyInfo pi
    = null;
    T model;
    foreach (DataRow dr in dt.Rows)
    {
    model
    = type.Assembly.CreateInstance(typeName) as T;
    foreach (DataColumn dc in dt.Columns)
    {
    if (!dictCols.ContainsKey(dc.ColumnName))
    {
    continue;
    }

    pi
    = type.GetProperty(dc.ColumnName);
    pi.SetValue(model, dr[dc.ColumnName]
    != DBNull.Value ? dr[dc.ColumnName] : null, null);
    }

    lisORM.Add(model);
    }
    }

    数据已填充完毕,最后剩下的就对集合进行监听,并触发监听事件。
    至于监听时机,由于每对集合进行的修改操作都将被监听对象侦听到,因此,在第一次填充数据的时候,监听理应尚未开始,所以最好的监听时机应该在数据填充完毕后进行最好,在Fill方法中加入监听事件://侦听ORM列表改变事件
    lisORM.AddEvent += new ORMList<T>.ORMEventHandler(OnModelAdded);
    lisORM.RemoveEvent += new ORMList<T>.ORMEventHandler(OnModelRemove);
    lisORM.UpdateEvent += new ORMList<T>.ORMEventHandler(OnModelUpdate);

    监听到事件后,再来看看如何同步更新DataSet,以新增操作为例,同样通过反射获取对象信息,并将信息填充于DataSet中去:
    /// <summary>
    /// 自定义ORM列表,添加事件
    /// </summary>
    private void OnModelAdded(object sender, ORMListEventArgs<T> e)
    {
    DataRow dr
    = dt.NewRow();
    dr.BeginEdit();
    PropertyInfo pi
    = null;
    foreach (string keyCol in dictCols.Keys)
    {
    pi
    = type.GetProperty(keyCol);
    try
    {
    dr[keyCol]
    = Convert.ChangeType(pi.GetValue(e.Model, null), dt.Columns[keyCol].DataType);
    }
    catch (Exception)
    {
    dr[keyCol]
    = DBNull.Value;
    }
    }

    dr.EndEdit();
    dt.Rows.Add(dr);
    }

    至此,ORM已实施完毕,只是一至到现在,我们还没有看到集合最终是如何更新到数据库中去的。实际上很简单,既然是在原有数据访问基础之上做的扩展,那么最终也应该由数据执行者来提交数据。

    放出ORMapping的具体实现及帮助类:
    View Code
    public class ORMapping<T> where T : class
    {
    #region 变量区
    private DataTable dt = null;
    private IMappingExecute executeObj;
    private ORMList<T> lisORM = null; //ORM列表
    private DataMapping map = null; //数据映射
    private Type type = null; //对象模型类型
    Dictionary<string, bool> dictCols = null; //列字段名
    private string tableName = ""; //物理表名
    private string columns = ""; //列字段名
    private string keyColumns = ""; //主键字段名
    #endregion

    #region 构造函数
    private ORMapping()
    {
    //不允许无数据执行者的构造函数
    }

    public ORMapping(IMappingExecute executeObj)
    {
    lisORM
    = new ORMList<T>();
    type
    = typeof(T);
    GetModelInformation();

    //获取数据执行者
    this.executeObj = executeObj;
    map
    = new DataMapping(this.executeObj, tableName, keyColumns, columns);
    }
    #endregion

    #region 公有方法

    /// <summary>
    /// 填充对象模型
    /// </summary>
    public void Fill(string sqlText)
    {
    //填充DataSet
    map.Fill(sqlText, tableName);
    dt
    = executeObj.GetDataSet().Tables[tableName];

    //填充对象模型
    string typeName = ORMHelper.GetTypeName(type) ;
    PropertyInfo pi
    = null;
    T model;
    foreach (DataRow dr in dt.Rows)
    {
    model
    = type.Assembly.CreateInstance(typeName) as T;
    foreach (DataColumn dc in dt.Columns)
    {
    if (!dictCols.ContainsKey(dc.ColumnName))
    {
    continue;
    }

    pi
    = type.GetProperty(dc.ColumnName);
    pi.SetValue(model, dr[dc.ColumnName]
    != DBNull.Value ? dr[dc.ColumnName] : null, null);
    }

    lisORM.Add(model);
    }

    //侦听ORM列表改变事件
    lisORM.AddEvent += new ORMList<T>.ORMEventHandler(OnModelAdded);
    lisORM.RemoveEvent
    += new ORMList<T>.ORMEventHandler(OnModelRemove);
    lisORM.UpdateEvent
    += new ORMList<T>.ORMEventHandler(OnModelUpdate);
    }
    #endregion

    #region 支撑方法
    /// <summary>
    /// 获取对象模型信息
    /// </summary>
    private void GetModelInformation()
    {
    //获取所有字段、主键
    dictCols = ORMHelper.GetColumns(type.GetProperties());
    foreach (string key in dictCols.Keys)
    {
    if (dictCols[key])
    {
    keyColumns
    += string.Format(", {0}", key);
    }

    columns
    += string.Format(", {0}", key);
    }

    if (keyColumns.Length == 0)
    {
    throw new Exception("请使用[ORMAttribute(IsKey = true)]设置主键");
    }

    keyColumns
    = keyColumns.Remove(0, 2);
    columns
    = columns.Remove(0, 2);

    //获取表名
    tableName = ORMHelper.GetTableName(type);
    }
    #endregion

    #region 事件方法
    /// <summary>
    /// 自定义ORM列表,添加事件
    /// </summary>
    private void OnModelAdded(object sender, ORMListEventArgs<T> e)
    {
    DataRow dr
    = dt.NewRow();
    dr.BeginEdit();
    PropertyInfo pi
    = null;
    foreach (string keyCol in dictCols.Keys)
    {
    pi
    = type.GetProperty(keyCol);
    try
    {
    dr[keyCol]
    = Convert.ChangeType(pi.GetValue(e.Model, null), dt.Columns[keyCol].DataType);
    }
    catch (Exception)
    {
    dr[keyCol]
    = DBNull.Value;
    }
    }

    dr.EndEdit();
    dt.Rows.Add(dr);
    }

    /// <summary>
    /// 自定义ORM列表,移除事件
    /// </summary>
    private void OnModelRemove(object sender, ORMListEventArgs<T> e)
    {
    //根据主键获取移除行
    string where = "";
    PropertyInfo pi
    = null;
    foreach (string keyCol in dictCols.Keys)
    {
    if (!dictCols[keyCol])
    {
    continue;
    }

    pi
    = type.GetProperty(keyCol);
    where = string.Format(" AND {0} = '{1}'", keyCol, pi.GetValue(e.Model, null));
    }

    where = where.Remove(0, 5);
    DataRow dr
    = dt.Select(where)[0];
    dr.Delete();
    }

    /// <summary>
    /// 自定义ORM列表,更新事件
    /// </summary>
    private void OnModelUpdate(object sender, ORMListEventArgs<T> e)
    {
    //根据主键获取移除行
    string where = "";
    PropertyInfo pi
    = null;
    foreach (string keyCol in dictCols.Keys)
    {
    if (!dictCols[keyCol])
    {
    continue;
    }

    pi
    = type.GetProperty(keyCol);
    where = string.Format(" AND {0} = '{1}'", keyCol, pi.GetValue(e.Model, null));
    }

    where = where.Remove(0, 5);
    DataRow dr
    = dt.Select(where)[0];
    dr.BeginEdit();
    pi
    = null;
    foreach (string keyCol in dictCols.Keys)
    {
    if (dictCols[keyCol])
    {
    continue;
    }

    pi
    = type.GetProperty(keyCol);
    try
    {
    dr[keyCol]
    = Convert.ChangeType(pi.GetValue(e.Model, null), dt.Columns[keyCol].DataType);
    }
    catch (Exception)
    {
    dr[keyCol]
    = DBNull.Value;
    }
    }

    dr.EndEdit();
    }
    #endregion

    #region 获取ORM列表
    /// <summary>
    /// 获取自定义ORM列表
    /// </summary>
    public ORMList<T> GetORMList
    {
    get
    {
    return lisORM;
    }
    }
    #endregion
    }

    public class ORMHelper
    {
    #region 从模型中获取数据表名
    /// <summary>
    /// 从模型中获取数据表名
    /// </summary>
    public static string GetTableName(Type type)
    {
    ORMAttribute attr
    = null;
    object[] attributes = type.GetCustomAttributes(false);
    foreach (object o in attributes)
    {
    if (o.GetType() == typeof(ORMAttribute))
    {
    attr
    = (ORMAttribute)o;
    break;
    }
    }

    return attr.GetTableName();
    }
    #endregion

    #region 获取类型名称
    /// <summary>
    /// 获取类型名称
    /// </summary>
    public static string GetTypeName(Type type)
    {
    return string.Format("{0}.{1}", type.Namespace, type.Name);
    }
    #endregion

    #region 对对象模型进行赋值操作
    /// <summary>
    /// 对对象模型进行赋值操作
    /// </summary>
    public static void SetValue<T>(T model, Type type, DataRow dr, DataColumnCollection dcs) where T : class
    {
    PropertyInfo pi
    = null;
    foreach (DataColumn dc in dcs)
    {
    pi
    = type.GetProperty(dc.ColumnName);
    if (pi == null)
    {
    continue;
    }

    pi.SetValue(model, dr[dc.ColumnName],
    null);
    }
    }
    #endregion

    #region 获取主键集
    /// <summary>
    /// 获取主键集
    /// </summary>
    public static IList<string> GetKeyColumns(PropertyInfo[] pis)
    {
    IList
    <string> lisKeys = new List<string>();
    object[] attributes;
    ORMAttribute attribute
    = null;
    foreach (PropertyInfo pi in pis)
    {
    attribute
    = null;
    attributes
    = pi.GetCustomAttributes(false);
    foreach (object o in attributes)
    {
    if (o.GetType() != typeof(ORMAttribute))
    {
    continue;
    }

    attribute
    = (ORMAttribute)o;
    if (attribute.IsKey)
    {
    lisKeys.Add(pi.Name);
    }
    }
    }

    return lisKeys;
    }
    #endregion

    #region 根据属性获取所有字段
    /// <summary>
    /// 根据属性获取所有字段
    /// 返回字典值表示是否为主键
    /// </summary>
    public static Dictionary<string, bool> GetColumns(PropertyInfo[] pis)
    {
    Dictionary
    <string, bool> dictCols = new Dictionary<string, bool>();
    bool isKey = false;
    object[] attributes;
    ORMAttribute attribute
    = null;
    foreach (PropertyInfo pi in pis)
    {
    isKey
    = false;
    attribute
    = null;
    attributes
    = pi.GetCustomAttributes(false);
    foreach (object o in attributes)
    {
    if (o.GetType() == typeof(ORMAttribute))
    {
    attribute
    = (ORMAttribute)o;
    isKey
    = attribute.IsKey;
    break;
    }
    }

    if (attribute != null && attribute.IsExtend)
    {
    //丢弃扩展属性
    continue;
    }

    dictCols.Add(pi.Name, isKey);
    }

    return dictCols;
    }
    #endregion
    }

    最后看看如何使用:
    string sqlText = "SELECT * FROM TEST";
    DataExecutor exec
    = ExecutorFactory.Create();
    ORMapping
    <TestModel> map = new ORMapping<TestModel>(exec.GetInstant());
    map.Fill(sqlText);
    ORMList
    <TestModel> lisOrm = map.GetORMList;
    TestModel model
    = lisOrm.Find(item => item.ID == "09b221a8-2389-4d81-b3fe-68bc8419d27f");
    if(model != null)
    {
    model.NAME
    = "模型修改";
    lisOrm.Update(model);
    }

    exec.Update();

    关于ORM的实施,实际是在数据访问层之上再加了一层映射,因而在进行实际数据操作的时候是多做了一次处理,对于性能上是有一定损耗的,其好处也是显而易见的,更接近于OO思想,可读性提高。至于到底要不要使用ORM,得由具体使用场景及开发习惯来决定了。
     
  • 相关阅读:
    原来实现钉钉自动签到如此简单,每天准时上下班不是梦
    12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(下)
    12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(中)
    12306 抢票系列之只要搞定RAIL_DEVICEID的来源,从此抢票不再掉线(上)
    python 学习笔记之手把手讲解如何使用原生的 urllib 发送网络请求
    发生线上故障后问责是不是第一要务
    软件工程是否可以直接应用于小团队
    阶段性正确的一点记录
    Java7新特性
    从server.xml看Tomcat容器的层次结构
  • 原文地址:https://www.cnblogs.com/FlySoul/p/2061174.html
Copyright © 2011-2022 走看看