打造自己的数据访问层(续)-ORM实施方案
在前几篇打造自己的数据访问层中,已经实现了最基本的数据访问层,基于程序开发中ORM的火热程度,笔者一直在思考,我们的数据访问层是否也能引入ORM的概念,哪怕是最基础的实现。
既然有想法,就该动手实施。
ORM,说到底就是将对象实体与数据表之间建立映射关系。
实施的话,首先得先提一提反射及自定义属性,试想一下,有如下模型,如何将其与数据表建立关联?
自定义属性在此时就派上用场了,定义表名、主键,应用于模型之上,通过反射获取对应信息。
看看如何编写自定义属性,实质上只需继承Attribute即可,我们为属性标明表名、判断是否为主键,再判断是否为扩展(不需进行数据映射),就目前而言,有这些信息已足够了:
应用于对象模型之上:
定义了属性,就得获取相应信息,这时反射来了了,通过Type来获取属性实例,并进一步获取对象信息,这是的关键方法是GetCustomAttributes,用于获取自定属性集:
表名、字段、主键都已获取,映射的主体内容已完全获取,接下来思考的是如何与现有数据访问层进行关联。
回过头来看看数据访问都做了些什么:
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;
激活事件,以更新方法为例:
集合准备好了,再来就是要建立映射关系了,如数同据访问层的映射对象一样,这里同样需要一个映射对象,只是这里是的映射并不是与数据库实体间时行关联,而仅仅是对原数据访问层映射的一种重复利用而已,看看它应该具备的功能:
1、获取模型信息:表名、字段、主键。
2、将模型填充于集合当中。
3、对集合进行监听,当列表数据改变时,同步对DataSet进行修改。
第1点已经实现了,利用自定义属性。
看看第2点,填充对象集合,利用已有映射对象获取数据集,通过反射获取对象信息并进行赋值操作:
数据已填充完毕,最后剩下的就对集合进行监听,并触发监听事件。
至于监听时机,由于每对集合进行的修改操作都将被监听对象侦听到,因此,在第一次填充数据的时候,监听理应尚未开始,所以最好的监听时机应该在数据填充完毕后进行最好,在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中去:
至此,ORM已实施完毕,只是一至到现在,我们还没有看到集合最终是如何更新到数据库中去的。实际上很简单,既然是在原有数据访问基础之上做的扩展,那么最终也应该由数据执行者来提交数据。
放出ORMapping的具体实现及帮助类:
最后看看如何使用:
关于ORM的实施,实际是在数据访问层之上再加了一层映射,因而在进行实际数据操作的时候是多做了一次处理,对于性能上是有一定损耗的,其好处也是显而易见的,更接近于OO思想,可读性提高。至于到底要不要使用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,得由具体使用场景及开发习惯来决定了。