很多程序员或学习程序的人会遇到这样的问题:若像方法中传入引用类型对象添加到List<T>泛型集合或者ArrayList中时发现集合中所有数据都是相同的,而且是最后一条添加进去的值,我也遇到了同样棘手的问题,但问题终归是问题,不是“死题”,总归有解决的办法,当你解决了这个问题后发现也不过如此。
我在开发.net模型层框架时遇到了一个问题,先说说框架原理:
框架类似于java中的Hibernate,构建实体类,属性名称和数据库一致,不用编写实体对象对应的DAL层中给实体赋值和取值的诸多方法,直接着手业务逻辑。”网上这么多此类框架,为什么不用呢?还自己开发“ 问得好,开发此框架为了更好的理解面向对象编程、面向接口编程、面向切面编程的特性,加深对.net framework的掌握,就算这个框架用不上,但对于自己的技术成长是很有帮助的。由于是自己开发,一人之力势单力薄,没有加入通过XML配置接口和模型的关系和自动生成实体类代码的功能,在以后漫长的改进中应该能加入这些功能吧……
转入正题,框架的使用很简单,将实体类对象传入方法就能完成对数据的操作,可遇到一个问题,传入的对象往往执行方法后数据被修改,在方法中new一个新实例也不行,网上查过了,方法内new是不起作用,以前没听说啊?!还是详细说明吧,如我想传入一个对象,根据对象查询出此对象对应表中所有的数据,添加到List<T>中,循环在list.Add()方法中逐条插入对象,结果list中所有数据全部一样,在foreach中new object也不行,看代码:(以下代码多数被简化,为了体现思想而并非为了完全实现)
///<summary>
/// 假设有这么一个管理员实体类
///</summary>
public class ADMIN {
private int _ID;
private string _NAME;
private string _UNAME;
private string _PWD;
public int ID{get;set;} //get和set访问器代码略
public string NAME{get;set;}
public string UNAME{get;set;}
public string PWD{get;set;}
}
///<summary>
/// 管理员业务逻辑类
///</summary>
public class AdminLogic {
public List<ADMIN> GetAdmins(ADMIN admin){
List<ADMIN> list = new List<ADMIN>();
List<object> objList = Factory.EntityHelper.GetList(admin);
foreach(object var in objList){
list.Add((ADMIN)var);
}
return list;
}
}
///<summary>
/// 抽象工厂类
///</summary>
public class Factory {
private static string dbType = "Access"; //这里原本是从配置文件中读取的,为了简化代码就这么做吧
private static IDBHelper _DBHelper;
private static IEntityHelper _EntityHelper;
//获取数据访问基础类(helper助手类)
public static IDBHelper DBHelper{
get {
switch(dbType){
case "Access":
_DBHelper = new OleDbHelper();
break;
case "SqlServer":
_DBHelper = new SqlDbHelper();
break;
default:
break;
}
return _DBHelper;
}
}
//由于数据库之间存在结构和T-SQL的差异,所以也要抽象出数据库实体操作类
public static IEntityHelper EntityHelper{
get{
switch(dbType){
case "Access":
_EntityHelper = new OleDbEntityHelper();
break;
case "SqlServer":
_EntityHelper = new SqlDbEntityHelper();
break;
default:
break;
}
return _EntityHelper;
}
}
}
///<summary>
/// IDBHelper接口和IEntityHelper接口我们只做查询的定义,因为问题是出在查询上,其他操作略
///</summary>
public interface IDBHelper {
//传入SQL语句和参数列表查询,返回DataTable
DataTable ExecuteQuery(string sql,params DBParameter[] parameters);
}
///<summary>
/// 实体对象操作助手类接口
///</summary>
public interface IEntityHelper{
//传入对象查询数据库对应数据表,并返回对象集合
List<object> GetList(object obj);
}
///<summary>
/// Access数据库访问基类(助手类)
///</summary>
public class OleDbHelper : IDBHelper {
public DataTable ExecuteQuery(string sql,params DBParameter[] parameters){
//实现代码略
}
}
///<summary>
/// SqlServer数据库访问基类(助手类)
///</summary>
public class SqlDbHelper : IDBHelper {
public DataTable ExecuteQuery(string sql,params DBParameter[] parameters){
//实现代码略
}
}
///<summary>
/// SqlServer数据实体助手类
///</summary>
public class SqlDbEntityHelper : IEntityHelper {
//由于我们用Access数据库举例,所以这里的内容略掉
}
///<summary>
/// Access数据实体助手类
///</summary>
public class OleDbEntityHelper : IEntityHelper {
//获取查询结果的对象集合
public List<object> GetList(object obj){
string sql = "";
OleDbParameter[] parameters = new OleDbParameter[];
//
//生成SQL语句和添加参数列表的代码略
//
DataTable dt = Factory.DBHelper.ExecuteQuery(sql,paramters);
List<object> list = new List<object>();
foreach(DataRow var in dt.Rows){
list.Add(GetEntityFromDataRow(obj,var));
}
return list;
}
//从数据行中解析出实体对象
//问题就出现在这个方法,我们把这个方法的内部具体来实现
private object GetEntityFromDataRow(object obj,DataRow dr){
//获取obj对象的类型
Type modelType = obj.GetType();
//遍历属性信息
foreach (PropertyInfo p in modelType.GetProperties()) {
//根据对象属性名称从数据行中获取对应字段的值并赋值给对象的属性
p.SetValue(obj, GetDefaultValue(dr[p.Name], p.PropertyType), null);
}
return obj;
}
//如果对象的值为对应数据库的空值,则返回数据类型默认的空值,否则返回此类型的非空值
private static object GetDefaultValue(object obj, Type type) {
if (obj == DBNull.Value) {
obj = default(object);
} else {
obj = Convert.ChangeType(obj, type);
}
return obj;
}
}
///<summary>
/// 测试类
///</summary>
public class Test{
static void Main(string[] args){
AdminLogic adminLogic = new AdminLogic();
ADMIN admin = new ADMIN();
//如果我要查询所有真实姓名为“张三”的管理员,只需要为一个新的ADMIN实例中NAME属性赋值就可以了。
admin.NAME = "张三";
List<ADMIN> list = adminLogic.GetAdmins(admin);
foreach(ADMIN var in list){
Console.Writeline(var.ID);
}
}
}
代码到此结束
好家伙,运行结果全部一样,调试后发现问题出在 OleDbEntityHelper类的 GetList方法中循环添加list值的代码处,添加的后一个值会覆盖性更改list中所有之前添加的值,我想到了obj对象是引用类型,list中的所有元素的指针都指向同一对象,要new一个新实例再添加,于是把这部分代码修改为:
public List<object> GetList(object obj){
//...
foreach(DataRow var in dt.Rows){
object newObj = new object();
newObj = GetEntityFromDataRow(obj,var);
list.Add(newObj);
}
return list;
}
运行结果还是一样…… 后来仔细想想,这不是换汤不换药嘛?!于是追踪到 GetEntityFromDataRow中才确认了是这个方法中的问题。
于是在此方法中new一个object出来:
private object GetEntityFromDataRow(object obj,DataRow dr){
object newObj = new object();
newObj = obj;
//获取obj对象的类型
Type modelType = newObj.GetType();
//遍历属性信息
foreach (PropertyInfo p in modelType.GetProperties()) {
//根据对象属性名称从数据行中获取对应字段的值并赋值给对象的属性
p.SetValue(newObj, GetDefaultValue(dr[p.Name], p.PropertyType), null);
}
return newObj;
}
我真想把我自己给杀了,这依然是换汤不换药啊,newObj的指针任然指向的是obj对象……
后来在网上查了很多关于对象克隆、深复制、浅复制的文章,测试无果……
踏破铁鞋无觅处,在挣扎了整整一天没有能解决这个问题后,第二天来上班随便在百度上一搜,百度真贱…… 昨天怎么不给我这个答案:
利用System.Activator对象动态创建实例就能解决这个问题,也怪我对.net了解匮乏... 代码修改如下:
private object GetEntityFromDataRow(object obj, DataRow dr) {
object newObj = new object();
newObj = Activator.CreateInstance(obj.GetType());
//获取obj对象的类型
Type modelType = newObj.GetType();
foreach (PropertyInfo p in modelType.GetProperties()) {
p.SetValue(newObj, GetDefaultValue(dr[p.Name], p.PropertyType), null);
}
return newObj;
}
解决了…… 结笔
以上代码应该可以复制运行,不过略掉的地方需要自己填上,由于是自己手动写的代码,可能代码中会出现一些错误,为了方便大家阅读代码样式也是自己编辑的。