CN.Text开发笔记—利用反射将数据读入实体类

在实际开发中,我们经常需要从数据库中读取数据并赋值给实体类的相应属性。在.Text的DataDTOProvider中存在大量这样的代码, 比如:

public Role[] GetRoles(int BlogID)
        
{
            System.Collections.ArrayList al
=new System.Collections.ArrayList();
            IDataReader reader
=DbProvider.Instance().GetRoles(BlogID);
            
try
            
{
                
while(reader.Read())
                
{
                    Role role
=new Role();
                    
if(reader["RoleID"]!=DBNull.Value)
                    
{
                        role.RoleID
=(int)reader["RoleID"];
                    }

                    
if(reader["Name"]!=DBNull.Value)
                    
{
                        role.Name
=(string)reader["Name"];
                    }

                    
if(reader["Description"]!=DBNull.Value)
                    
{
                        role.Description
=(string)reader["Description"];
                    }

                    
//ReaderToObject(reader,role);
                    al.Add(role);
                }

            }

            
finally
            
{
                reader.Close();
            }

            
return (Role[])al.ToArray(typeof(Role));
        
        }


对于上面的代码,我觉得有几点不优雅之处:
1、每次对Role的属性进行赋值时,都要检查reader的值是否为DBNull,出现了很多重复代码
2、每次对Role的属性进行赋值时,都要进行类型转换, 而Role属性的类型是已知的,是不是可以自动完成这样的转换?
3、每次对Role的属性进行赋值时,都要进行Role属性与数据库字段的对应。如果我们在设计数据库与实体类时,保证数据库字段与实体类属性采用同样的名称,那利用反射,我们可以通过代码自动进行属性与字段的对应。即使数据库字段与属性不同名,我们也可以通过更改查询语句,来做到这一点。
是不是可以对上面的代码进行改进,使代码变得更优雅?那优雅的代码应该是什么样的呢?如果我们用上面代码中注释的代码行ReaderToObject(reader,role);取代它之前的对Role属性进行赋值的语句,是不是会使代码变得更优雅?ReaderToObject的作用就是自动完成将reader中的值写入到role中对应的属性中(前提是reader中的字段与role中对应的属性具有相同的名称)。现在我们的任务就是实现ReaderToObject, 有了强大的武器—Reflection,我们的任务就变得很轻松, 也不多说了,下面的代码是我的实现方法:

private void ReaderToObject(IDataReader reader,object targetObj)
        
{
            
for(int i=0;i<reader.FieldCount;i++)
            
{
                System.Reflection.PropertyInfo propertyInfo
=targetObj.GetType().GetProperty(reader.GetName(i));
                
if(propertyInfo!=null)
                
{
                    
if(reader.GetValue(i)!=DBNull.Value)
                    
{
                        propertyInfo.SetValue(targetObj,reader.GetValue(i),
null);
                    }

                }

            }

        }

ReaderToObject可以将reader中的数据读入到任何实体类中。数据库字段与实体类属性的映射原则是名称相同。当然,我们也可以通过配置文件来进行两者映射。

    个人想法:在开发中,面对那么多设计思想和设计模式,常常令人感到迷惑,当你把更多的精力放在选用哪个设计思想或设计模式时,我觉得不要忽略很重要的一点,尽可能地减少重复代码,只要我们能有效地减少重复代码,我们采用的方法就是好方法,而不要太在乎采用了哪种模式。就像独孤九剑,正因为摆脱了传统招式的束缚,才能战无不胜!

posted @ 2004-09-12 17:40 dudu 阅读(5429) 评论(20)  编辑 收藏

  回复  引用    
#1楼 2004-09-12 18:14 | JGTM'2004 [MVP] [未注册用户]
在敏捷实践中,重构是编码与设计过程中最重要的技术之一了,而模式是大方向(这背后还有一些OOAD的大原则,我会在最近的文章中联系起来阐述一下)——虽然23种模式中没有你用到的这一种,但是面向元数据的开发中确实也有一些(可能尚未成文的)模式——比如说你这段代码,在我之前写的ElegantDAL子系统中就几乎相同的出现过(这里我提示你处理Enum类型的时候需要绕一些小弯路,因为在数据库中一般是存为int或varchar的,这在SetValue的时候会出问题的——不过还是先留给你自己探索吧,不然多没乐趣呀!:)。

Great job. And enjoy coding!
  回复  引用  查看    
#2楼 2004-09-12 18:51 | 吕震宇      
@ JGTM'2004 [MVP]

哈哈,是不是在这里:

http://blog.joycode.com/jgtm2000/archive/2004/02/18/13294.aspx

正在读。“面向元数据的开发”,这背后含义颇多呀!很有启发性!


  回复  引用  查看    
#3楼 2004-09-12 20:48 | wayfarer      
一直期待dudu能把.Text的开发心得写出来,现在终于等到了,不顶不行啊:) 不过,更希望能有个系列,而非以这种散列化的方式表现出来。
  回复  引用  查看    
#4楼 2004-09-12 21:09 | dudu      
谢谢JGTM'2004 [MVP] 的指点。
Enum的确存在你所说的问题,参考你的文章:.NET Quick Tip: Runtime Type Conversion for Enumeration ,现在我将ReaderToObject代码改成这样:
private void ReaderToObject(IDataReader reader,object targetObj)
        
{
            
for(int i=0;i<reader.FieldCount;i++)
            
{
                System.Reflection.PropertyInfo propertyInfo
=targetObj.GetType().GetProperty(reader.GetName(i));
                
if(propertyInfo!=null)
                
{
                    
if(reader.GetValue(i)!=DBNull.Value)
                    
{
                        
if(propertyInfo.PropertyType.IsEnum)
                        
{
                            propertyInfo.SetValue(targetObj,Enum.ToObject(propertyInfo.PropertyType,reader.GetValue(i)),
null);
                        }

                        
else
                        
{
                            propertyInfo.SetValue(targetObj,reader.GetValue(i),
null);
                        }

                    }

                }

            }

        }

  回复  引用  查看    
#5楼 2004-09-12 21:28 | dudu      
@wayfarer
我会尽力在开发CN.Text过程中多写一些心得。
  回复  引用    
#6楼 2004-09-13 02:02 | lonelystranger [未注册用户]
CN.Text这个名字起得不错
  回复  引用  查看    
#7楼 2004-09-13 04:25 | kwklover      
@dudu
用这中方法做的程序在性能上怎么样?

第一种,也就是你认为不优雅的和第二中优雅的方法在性能上有差别吗?差别多大?

能不能做一下分析呢?
谢谢
  回复  引用    
#8楼 2004-09-13 08:34 | jiezhi [未注册用户]
反射能不用就不用,反射会有比较大的代价
  回复  引用    
#9楼 2004-09-13 09:25 | engine [未注册用户]
to 楼上:
反射代价很大吗?只是给对象设置公共属性,会生产多大代价???
作者认为呢?!
  回复  引用  查看    
#10楼 2004-09-13 15:43 | lion      
很好的技巧,只是觉得对性能上的拖累太多

  回复  引用    
#11楼 2004-09-14 08:39 | jiezhi [未注册用户]
to engine :
使用反射的性能是最差的。这个不需要我多言了吧
  回复  引用    
#12楼 2004-09-14 09:43 | longzx [未注册用户]
csopf是一个C# Object Persistent Framework,可以参考一下,http://csopf.sourceforge.net/ 。
我个人认为,使用attribute和reflection来完成persistent工作,会让entity Object变得过于肥大,失去了灵活性,要求类成员和表必须有很好的映射关系,功能也会受到限制;理想的做法是建立一个更简单的Persistent Framework,为每个entity object 定义一个persitstentWrapper,专门负责persistent的工作。
举个例子,如果entity object中有一个只读属性,只能在构造函数中初始化,那你的解决办法就不灵了。我认为.text原来的实现还是在减少重复代码和保持灵活性上取得了很好的平衡。
  回复  引用  查看    
#13楼 2004-09-14 10:02 | dudu      
反射对性能的影响是勿容置疑的, 但我们不能因为这个原因就拒绝使用反射, 我们需要知道反射对性能的影响究竟多大, 使用反射是否利大于弊?请看一下这篇文章, 该文章对反射对性能的影响进行了分析与测试:
http://west-wind.com/weblog/posts/351.aspx
我们不能完全从性能的解度去考虑是否采用一项技术, 如果这样的话, 我们为什么不直接用汇编, 为什么还会有那么多高级语言, 为什么还会出现虚拟机?
  回复  引用    
#14楼 2004-09-30 15:09 | yoonkit [未注册用户]
Thanks longzx for the recommendation.
I use reflection to extract the metadata of the class so that the mapping to the rdbms is transparent to the developer.

However I have also introduced a MetaInfo class which caches the reflection, so everytime we access the properties, it doesnt go through reflection but goes through the meta-info which has already been extracted.

This has a small performance hit over direct props/fields, but the hit is largely minimal, and the benefits to the maintenance to the code far outweighs the so called performance loss.

Feedback is truly welcomed!

yoonkit.
  回复  引用    
#15楼 2005-01-18 15:57 | exhjw [未注册用户]
good!

不能因为汽车会有尾汽排出而拒绝乘坐
  回复  引用    
#16楼 2005-08-15 15:16 | 小鸡鸡憋住==鸡憋 [未注册用户]
我在最近的项目开发中也用了楼上所讨论的技术,即自动DATASET对象(暂时只支持DATASET对象,没有DATAREADER)将内容读取到实体类,而且可以自动创建外键对象(比如一个订单主体有多笔明细这样)。。。

结论是反射所消耗的资源是非常巨大的,读取1000笔的主从记录(测试用),消耗了将近10秒的时间,估计这个速度在比较频繁的应用应该是会被客户干操的...所以我的建议是,用SINGLETON方式来保存实体类,然后在系统启动的初始化阶段利用工作线程来读取记录。
  回复  引用    
#17楼 2006-01-11 15:25 | 不会飞的鱼 [未注册用户]
直接调用和反射的性能差距是250倍左右, 但是现在的计算机已经足够快了,在不是对性能要求高的地方,我想反射带来的代码和维护简化比性能的损失要大得多
  回复  引用  查看    
#18楼 2006-10-06 00:07 | LongMarch      
Enum的问题在.net2.x中已经不存在了,就算SetValue一个Enum定义范围之外的值也一样成功!

  回复  引用  查看    
#19楼 2007-04-29 17:03 | Lynn      
看似是个很好的技巧,但是我觉得有两大不妥:
1. 反射性能造成了性能不够好;
2. 如果把数据库字段名和实体类名称一一对应的话,就造成了数据库与用到该实体类的所有层进行了强耦合,打个比方,如果我更改一下数据库字段名称,势必造成所以用到该实体的代码都要修改。当然利用配置文件进行名称的对应可以避免这个问题。
  回复  引用  查看    
#20楼 2007-07-27 17:52 | 镜涛      
反射自然有它值得我们用的地方,读了这篇文章还是学到了技巧!

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  博客园首页

  新闻频道

  社区

  小组

  博问

  网摘

  闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2004-12-11 10:46 编辑过
成果网帮您增加网站收入


相关链接: