帅呆了,自己动手解除一个对DataReader对象的疑惑

  和某个新认识的有多年开发经验的高手聊天,他出了个题考楼猪。问题说起来其实灰常常见和简单,用代码描述就是这样的:

IDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
  int numProp = (int)rdr[0]; //编译通过否?
}

  虽然楼猪这段掐头去尾的代码看上去很是恶心,但是对于很多初学.net的童鞋而言,刚开始照着教科书也没有少写这种代码吧?您看懂楼猪要表达的意思了吗?还是由楼猪先对上面的代码稍做解释:cmd是SqlCommnd对象的一个实例,现在通过一板一眼地写ado.net几个常用对象的的方式从数据库取数据(众所周知,在实际的开发中这样写会有非常多的重复,然后就出现了大名鼎鼎的sqlhelper,然后各式各样的orm...是不是顺理成章?),我们假定rdr执行了ExecuteReader方法后,从数据库里取回了一系列数据。假设其中索引为0的那列为整型,我们直接写 int numProp = (int)rdr[0];这一句,可以得到某一条记录某一列(这里就是索引为0的那列)的值。
  对于 int numProp = (int)rdr[0];这种“暴力”写法,楼猪一开始就敏锐地认识到这样进行数据转换有可能会失败。这个很好理解,因为如果rdr[0]对应的object为null,强制转换当然会抛出异常。对于这点,那位高手他也是认同的。高兴还没来得及传导到楼猪的神经末梢,他又面露狡色地问这行代码编译能不能通过。自从被编译器摆了一道后,楼猪说话就小心了不少,再加上他那气定神闲好像在说“小子,这次你可蒙不上来啦...”的BS人的小眼神,楼猪不禁有点心虚。考虑了约莫两秒,本着柏拉图的“吾爱吾师,吾更爱真理”的精神,楼猪还是挣扎着以灰常肯定的口气回答说可以编译通过。想不到啊,真是想不到啊,分歧竟然就在这里!那个高手以坚定而犀利的眼神信誓旦旦地说如果这么写的话编译器肯定是不会通过的,真不知道他哪里来的自信?!
  但是,why?为什么会编译不通过呢?楼猪左思右想就是感觉哪里不对劲,真的没有任何道理嘛。难道楼猪自己已经out了,或是一时脑袋秀逗了?以楼猪一贯的固执臭美和自以为是的作风,再加上自己已经对ado.net有了一部分积累,嗯......还真不能贸然断定自己有没有犯错。还是做个简单试验最保险最有说服力: 
1、实体类及相关
a、Person实体类

代码
   public class Person : BaseQuery
    {
        
public int Id { getset; }
        
public string FirstName { getset; }
        
public string LastName { getset; }
        
public double Weight { getset; }
        
public double Height { getset; }
    }

 b、查询基类(BaseQuery)

代码
    public class BaseQuery
    {
        [NotColumnAttribute(Description 
= "非真实表对应列")]
        
public double MinHeight { getset; }

        [NotColumnAttribute(Description 
= "非真实表对应列")]
        
public double MaxHeight { getset; }
    }

 c、NotColumnAttribute特性

   public class NotColumnAttribute : Attribute
    {
        
public string Description { getset; }
    }

 2、ado.net部分

代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;

public class SqlHelper
{
    
private const string strSqlCon = @"Data Source=.\sqlexpress; Initial Catalog=TestDb; User Id=sa; Password=123456;";

    
/// <summary>
    
/// 查询数据表项并转换为对应实体
    
/// </summary>
    
/// <typeparam name="T"></typeparam>
    
/// <param name="objType"></param>
    
/// <param name="strSelectSql"></param>
    
/// <returns></returns>
    public static IList<T> Select<T>(Type objType, string strSelectSql) where T : classnew()
    {
        IList
<T> listModels = new List<T>();
        
using (SqlConnection sqlConn = new SqlConnection(strSqlCon))
        {
            sqlConn.Open();
            SqlCommand cmd 
= new SqlCommand();
            cmd.Connection 
= sqlConn;
            cmd.CommandText 
= strSelectSql;
            IDataReader rdr 
= cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
            
while (rdr.Read())
            {
                Object obj 
= Activator.CreateInstance(objType);
                PropertyInfo[] properties 
= objType.GetProperties();
                
foreach (PropertyInfo propInfo in properties)
                {
                    
object[] objNotColumns = propInfo.GetCustomAttributes(typeof(NotColumnAttribute), true);
                    
if (objNotColumns != null && objNotColumns.Length > 0continue;
                    
int index = rdr.GetOrdinal(propInfo.Name);
                    
//int numProp = (int)rdr[0]; // int numProp = (int)rdr[propInfo.Name]; //如果某个属性是某一类型的数据,可以直接这么转换
                    if (index > -1 && rdr.GetValue(index) != System.DBNull.Value)
                        propInfo.SetValue(obj, rdr.GetValue(index), 
null);
                }
                T model 
= default(T);
                model 
= obj as T;
                listModels.Add(model);
            }
        }
        
return listModels;
    }

}

 3、客户端测试 

static void Main(string[] args)
{
     
string sql="SELECT Id,FirstName,LastName,Weight,Height FROM Person";
     SqlHelper.Select
<Person>(typeof(Person), sql);
     Console.ReadKey();
}

 补充说明:
a、在查询时,我们依然沿用这一篇里的Person表,进行查询。
b、实体类继承的基类是一个查询条件(从命名可以猜到是代表人的身高范围),而NotColumnAttribute这个特性表示基类里的两个属性在实际表里没有对应列。
c、在Select泛型方法中,注释掉的那一行您可以取消注释试一试,楼猪可以非常肯定地说,在自己的机器上没有任何编译警告或错误提示(btw,这个方法有没有常见的orm的影子?可以自己扩展扩展试试看)。
  总结:经过楼猪试验,事实胜于雄辩,framework2.0及以上版本,直接写 int numProp = (int)rdr[0];这样的逻辑有潜在错误的代码编译都成功通过(c#编译器的魔法虽然很多,但是至少到目前为止它还没牛到能预判并检测运行时的逻辑吧,虽然楼猪也热切期待那种能完美检测出代码逻辑错误的更智能的编译器尽早面世)。高手啊高手,您果然是在考验楼猪呢。从这里楼猪也领会到,在你认为明显有悖于事实的情况下,对某些见多识广的前辈高手的金玉良言适当甄别一下还是必须的,否则真不知道自己的智商到底会不会出问题。

posted on 2010-03-13 10:13  JeffWong  阅读(1068)  评论(7编辑  收藏  举报