一步一步实现数据库到类的自动化映射(一) 类层次的设计 类的实现
上一篇讲了很多废话,结果才讲到了工具类。实在是有点浪费大家的时间。这次我就不多说了。马上来看看设计思路:我的目标是设计完成后。这个框架要可以使用工具来根据数据库表的结构自动生成类的代码,以便减少工作量。因此,我打算把类设计成只包含表的字段的所需信息。其它保存、和查询的操作都在基类里实现(好像是废话,人人都是这样设计类的-_-!)。
因此,我要有一个Attribute类来说明类的属性对数据表之间的映射(NHibernate是用XML来说明的,我不想搞得那么麻烦、方便就好)。
以一段设计好的代码来说明一下问题。
/// <summary>
///字段 T_VarChar
///数据库里的数据类型 :VarChar ,SqlDbType :VarChar
///说明: 字段长度:60 字段小数位数0
///默认值: 是否允许为空:True
/// </summary>
public event EventHandler T_VarCharChanged;
private string mT_VarChar=null;
[SqlFieldAttribute(SqlDbType = System.Data.SqlDbType.VarChar,FieldName = "T_Name")]
public string T_VarChar
{
get{return mT_VarChar;}
set
{
mT_VarChar=value;
if (T_VarCharChanged!=null)
T_VarCharChanged(this,new EventArgs());
}
}
以上代码是一个类里的属性。他有一个自定义属性:
[SqlFieldAttribute(SqlDbType = System.Data.SqlDbType.VarChar,FieldName = "T_Namne")]
这个东西说明了以下信息:这个类的属性在数据库中将作为VarChar类型,它所映身到表的T_Name字段。
SqlFieldAttribute 类的定义如下:
public class SqlFieldAttribute : Attribute
{
public System.Data.SqlDbType SqlDbType = SqlDbType.VarChar;
public string FieldName = "";
public bool Saveable = true;
}
子类还要重写(override) 基类的TableName方法,来获取这个类是映射哪一个表的。这个方法返回表名,如:
public override string TableName()
{
return "T_Test1";//这个类是对表T_Test1进行操作的。
}
这样表和字段的映射关系都反映出来了,那么任何一个类都可以通过反射生成Sql语句来进行操作了。并且子类中就只包括了数据表的字段。这样将业要自动生成代码也是比较容易的。
重点来了:
本来Insert和Update操作可以放在基类里实现在的。可是为了要实现“事务操作”(同时确保几个类的保存成功与失败)。还是要另外编写一个工具类来保存这些信息。
这里先不看工具类,先看看基类是怎么实现的。
using System;
using System.Data;
using YZPTool;
namespace YZPTool.Data
{
/// <summary>
/// SqlDbRecordAbstract 的摘要说明。
/// </summary>
public abstract class SqlDbRecordAbstract
{
private int m_id1 = 0;
private int m_id2 = 0;
public int ID
{
get{return m_id1;}
}
public abstract string TableName();
/// <summary>
/// 从一条记录里读取信息给自己
/// </summary>
/// <param name="aDataRow">包含所有需要的信息的记录</param>
public void LoadFromDataRow(DataRow aDataRow)
{
YZPTool.Data.DbClassTool.LoadFromDataRow(aDataRow,this);
}
/// <summary>
/// 保存到数据库
/// </summary>
public void Save()
{
YZPTool.Data.DbClassTool.Save(this);//调用工具类的方法来保存自已
}
/// <summary>
/// 对表进行查询。
/// </summary>
public DataTable GetDataTable(string conditionString)
{
if ((conditionString!=null)||(conditionString!=""))
return DBManager.GetDataTable("select * from "+this.TableName()+" where "+conditionString);
else
return DBManager.GetDataTable("select * from "+this.TableName());
}
}
}
要注意的地方是有两个私有的m_id成员.m_id1、m_id2;
m_id1是这个记录在数据库里的ID标识。当它>0时表示这个类已经是保存了在数据库里的了。回些调用Save时就是执行Update操作。如果是<0就调用Insert操作。
那为什么还有个m_id2呢。因为在事务操作时。要确保所有要保存的对像都成功保存到数据库了才能真正地更新m_id1这个值。在事务没有Comit前。都只是先把插入后的值放在了m_id2里暂时保存。
以下是通过反射来保存工具类的实现:
using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using YZPTool;
namespace YZPTool.Data
{
public class SqlFieldAttribute : Attribute
{
public System.Data.SqlDbType SqlDbType = SqlDbType.VarChar;
public string FieldName = "";
public bool Saveable = true;//这字段是否要更新到数据库。
}
/// <summary>
/// DbClassTool 的摘要说明。
/// </summary>
public class DbClassTool
{
/// <summary>
/// 这个是类包含了保存到数据库时的所有所需的信息如:字段名、字段类型,字段的值。
/// </summary>
protected class SqlField
{
public object FieldValue = null;
public string FieldName = null;
public SqlDbType SqlDbType = SqlDbType.VarChar;
public bool Saveable = true;
public SqlField(string aFieldName,object aFieldValue,SqlDbType aSqlDbType,bool saveable)
{
FieldName = aFieldName;
FieldValue = aFieldValue;
SqlDbType = aSqlDbType;
Saveable = saveable;
}
}
private DbClassTool()
{}
/// <summary>
/// 获取一个类中包含的记录信息(SqlFiell
/// </summary>
/// <param name="obj">要对其操作的类</param>
/// <returns>所有字段的信息:类型,值,字段名</returns>
protected static IList GetSqlFields(SqlDbRecordAbstract obj)
{
ArrayList arrList = new ArrayList();
Type type = obj.GetType();
PropertyInfo[] propertyInfos = type.GetProperties(System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Public);
foreach (PropertyInfo fi in propertyInfos)
{
if (fi.IsDefined(typeof(SqlFieldAttribute),true))
{
SqlFieldAttribute sfa = (SqlFieldAttribute)fi.GetCustomAttributes(typeof(SqlFieldAttribute),true)[0];
SqlField sf = new SqlField(sfa.FieldName,fi.GetValue(obj,null),sfa.SqlDbType,sfa.Saveable);
arrList.Add(sf);
}
}
return arrList;
}
protected static void Insert(SqlCommand cmd,SqlDbRecordAbstract obj)
{
IList aSqlFields = DbClassTool.GetSqlFields(obj);
cmd.Parameters.Clear();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("Insert into "+obj.TableName()+" (");
bool addComma = false;
foreach (SqlField sf in aSqlFields)
{
if (!sf.Saveable)//如果不需要保存,就不管他
continue;
if (addComma)
sb.Append(",");//第一次个字段不加逗号
else
addComma = true;
sb.Append(sf.FieldName);
}
sb.Append(") values(");
addComma = false;
foreach (SqlField sf in aSqlFields)
{
if (!sf.Saveable)//如果不需要保存,就不管他
continue;
if (addComma)
sb.Append(",");//第一次个字段不加逗号
else
addComma = true;
sb.Append("@"+sf.FieldName);
//设置参数列表
System.Data.SqlClient.SqlParameter param = new System.Data.SqlClient.SqlParameter("@"+sf.FieldName,sf.SqlDbType);
if (sf.FieldValue!=null)
param.Value = sf.FieldValue;
else
param.Value = DBNull.Value;
cmd.Parameters.Add(param);
}
//sb.Append(");select @@IDENTITY as ID");
sb.Append(");select Max(id) as ID from "+obj.TableName());
FieldInfo fi_m_id2 = typeof(SqlDbRecordAbstract).GetField("m_id2",BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
cmd.CommandText = sb.ToString();
object idValue = int.Parse(cmd.ExecuteScalar().ToString());
//obj.m_id2 = int.Parse(idValue);
fi_m_id2.SetValue(obj,idValue);
}
protected static void Update(SqlCommand cmd,SqlDbRecordAbstract obj)
{
IList aSqlFields = DbClassTool.GetSqlFields(obj);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("Update "+obj.TableName()+" set ");
bool addComma = false;
foreach (SqlField sf in aSqlFields)
{
if (!sf.Saveable)//如果不需要保存,就不管他
continue;
if (addComma)
sb.Append(",");//第一次个字段不加逗号
else
addComma = true;
sb.Append(sf.FieldName+"=@"+sf.FieldName);
//设置参数列表
System.Data.SqlClient.SqlParameter param = new System.Data.SqlClient.SqlParameter("@"+sf.FieldName,sf.SqlDbType);
if (sf.FieldValue!=null)
param.Value = sf.FieldValue;
else
param.Value = DBNull.Value;
cmd.Parameters.Add(param);
}
sb.Append(" where ID="+obj.ID.ToString());
cmd.CommandText = sb.ToString();
cmd.ExecuteNonQuery();
}
/// <summary>
/// 外面就是调用这个方法来实现对象保存的事务机制的。
/// </summary>
/// <param name="aDbRecords"></param>
public static void Save(SqlDbRecordAbstract[] aDbRecords)
{
SqlConnection conn = DBManager.GetConnection();
conn.Open();
SqlTransaction transaction = conn.BeginTransaction();
SqlCommand sqlCommand = new SqlCommand("",conn,transaction);
try
{
//把类保存到数据库
foreach (SqlDbRecordAbstract obj in aDbRecords)
{
if (obj.ID<=0)
DbClassTool.Insert(sqlCommand,obj);
else
DbClassTool.Update(sqlCommand,obj);
}
//更新类的操作
foreach (SqlDbRecordAbstract obj in aDbRecords)
{
FieldInfo fi_m_id2 = typeof(SqlDbRecordAbstract).GetField("m_id2",BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
FieldInfo fi_m_id1 = typeof(SqlDbRecordAbstract).GetField("m_id1",BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
object idValue = fi_m_id2.GetValue(obj);
fi_m_id1.SetValue(obj,idValue);
//obj.m_id1 = obj.m_id2;
}
transaction.Commit();
}
catch (Exception ee)
{
transaction.Rollback();
throw ee;
}
conn.Close();
}
/// <summary>
/// 保存单个对象时就使用这个方法
/// </summary>
/// <param name="aDbRecord"></param>
public static void Save(SqlDbRecordAbstract aDbRecord)
{
Save(new SqlDbRecordAbstract[]{aDbRecord});
}
/// <summary>
/// 通过反射,从数把记录里读取信息。
/// </summary>
/// <param name="dataRow"></param>
/// <param name="obj"></param>
public static void LoadFromDataRow(DataRow dataRow,SqlDbRecordAbstract obj)
{
Type type = obj.GetType();
PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
FieldInfo fi_m_id2 = typeof(SqlDbRecordAbstract).GetField("m_id2",BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
FieldInfo fi_m_id1 = typeof(SqlDbRecordAbstract).GetField("m_id1",BindingFlags.NonPublic|BindingFlags.Public|BindingFlags.Instance);
fi_m_id2.SetValue(obj,dataRow["id"]);
fi_m_id1.SetValue(obj,dataRow["id"]);
foreach(PropertyInfo pi in propertyInfos)
{
if (pi.IsDefined(typeof(SqlFieldAttribute),true))
{
SqlFieldAttribute sfa = (SqlFieldAttribute)pi.GetCustomAttributes(typeof(SqlFieldAttribute),true)[0];
if (pi.CanWrite)
pi.SetValue(obj,dataRow[sfa.FieldName],null);
else
{
FieldInfo fi = type.GetField("m_"+pi.Name);
if (fi!=null)
fi.SetValue(obj,dataRow[sfa.FieldName]);
else
{
fi = type.GetField("m"+pi.Name);
if (fi!=null)
fi.SetValue(obj,dataRow[sfa.FieldName]);
}
}
}
}
}
}
}
总结:只需使用三个类就能实现数据库表到对象的映射。
YZPTool.Data.SqlDbRecordAbstract、YZPTool.Data.DbClassTool、YZPTool.Data.SqlFieldAttribute
使用方法:子类只要继YZPTool.Data.SqlDbRecordAbstract,使用YZPTool.Data.SqlFieldAttribute对自已的属性与数据库的映射关系进行描述。在需要保存时调用自已的Save方法就可以更新、保存到数据库了。如果要同时保存多个对象时就调用YZPTool.Data.DbClassTool的表态方法public static void Save(SqlDbRecordAbstract[] aDbRecords)。
这个设计这几个类的时间不多。可能还有很多地方不能满足需求。大家有什么意见、或有更深的体会。希望可以交流,我的目标是做出一个简单易用的数据库框架出来。减少日常开发的工作量:
Yzp0755@163.com
类的层次暂时到此结束,欢迎大家讨论。如果我讲得不好,大家不要说脏话啊^_^。
下一篇我将会说说如何实现自动化生成子类的代码(本来想赶紧完成的,不过现在都快过年了,没时间啊。呵呵!!)。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/koy0755/archive/2006/01/21/585384.aspx