/*
 * 我是由ASP转C#的,写C#也有两三年了,也作过几个小项目,因为一直都是一个人开发,所以许多东西都是google或是自己琢磨的,基本上是举步艰难,不过还算是乐在其中吧。
 * 因为是作的项目比较小,所以实体类基本上是一个核心部份了,所以对这部份东西比较上心,但是,没人交流,故,也不知道到底怎么样。
 * 在这里我把我实现实体类的方式共享出来,大家也请指点一下。
 * 
 * 简单说一下原理:
 * 把IDataReader转成Dictionary<string, object>,然后在读取时再赋值到相应的字段属性内。
 * 而关联表通过交流Dictionary<string, object>来实现关联表操作
 * 
 * 现在头疼的地方就是getParameter()方法,用这个方法我可以把简单的添加修改删除代码抽象出来,不用每个类去专门写一段添加修改删除的操作
 * 头疼的是,修改时也需要把整个实体类的所有字段都读一遍,这明显就是浪费了。
 * 
 * 我本身预想有两种方法,一是用switch,或是If,或是干脆再来一个方法专门给修改用。但是一直没有作出选择,不知道还有什么更好的办法处理不。
 */
//实体类的基类
public abstract class RecordBase
{
    //记录IDataReader数据集内的数据
    //此处的Dictionary<>,实际应用时我用的是一个object[]数据来作记录,获取字段值时才将数组转换成Dictionary<>来使用。这样实体类才实现能序列化。
    private Dictionary<string, object> _recordValue;
    protected Dictionary<string, object> recordValue
    {
        get { if (_recordValue == null) { _recordValue = new Dictionary<string, object>(); } return _recordValue; }
        set { _recordValue = value; }
    }    //接收IDataReader的数据
    public RecordBase(IDataReader dr)
    {
        this.recordValue = new Dictionary<string, object>(dr.FieldCount);
        for (int x = 0; x < dr.FieldCount; x++)
        {
            this.recordValue.Add(dr.GetName(x), dr.GetValue(x));
        }
    }
    //作为关联数据表
    public RecordBase(Dictionary<string, object> recordValue)
    {
        this.recordValue = recordValue;
    }    //获取字段值,之所以作成一个方法单独处理只是不想把try放得到处都是而以。
    private object getValue(string field)
    {
        object value = null;
        /*
         * 这里之所有用try而不用recordValue.ContainsKey来判断字段是否存在,
         * 是因为字段不存在这种错误是不应该出现的,如果出现那就是查询句或是别的地方有错。
         * 所以不需要用recordValue.ContainsKey来跳出找不到字段的错误,
         * 而用try来抛出错误的话,可以更好的提示程序员出错是什么原因。
         */
        try
        {
            
            //字段值为DBNull时返回null,让getFieldValue作适当的处理。
            if (!(recordValue[field] is DBNull)) { value = recordValue[field]; }
        }
        catch (Exception e) { throw new Exception(string.Format("找不以字段{0}", name)); }
        return value;
    }    #region 获取字段值
    //获取字段数据类型为 decimal 的字段值
    private decimal getFieldValue(ref decimal? properties, string fieldName)
    {
        if (!properties.HasValue) { properties = (decimal)(this.getValue(fieldName) ?? 0m); }
        return properties.GetValueOrDefault();
    }
    //获取字段数据类型为 string 的字段值
    protected string getFieldValue(ref string properties, string fieldName)
    {
        if (properties == null) { properties = (getValue(fieldName) ?? "").ToString(); }
        return properties;
    }
    /* 还有其它数据类型的字段提取方式这里就不作出来了,反正都差不多的 */
    #endregion
}
//实体类 添加修改删除操作 接口
public interface IRecordModify
{
    Dictionary<string, object> getParameter();//获取实体类的字段与字段值
    string getTableName();//因为Modify用的是泛型操作,所以需要获取列名的方法
    decimal Id { get; set; }//因为添加新记录时需要获取新记录的Id号
}
//Record数据表的实体类
public class Record : RecordBase, IRecordModify
{
    private decimal? _id;
    private string _name;    public Record() { }
    public Record(IDataReader dr) : base(dr) { }
    public Record(Dictionary<string, object> recordValue) : base(recordValue) { }    #region 字段
    public decimal Id
    {
        get { return this.getFieldValue(ref _id, FieldName.Id); }
        set { _id = value; }
    }
    public string Name
    {
        get { return this.getFieldValue(ref _name, FieldName.Name); }
    }
    #endregion    #region 关系表
    [NonSerialized]
    private Relations _relations;
    public Relations Relations
    {
        get
        {
            if (_relations == null) { _relations = new Relations(this.recordValue); }
        }
    }
    #endregion    //表名
    public const string TableName = "Record";
    //字段列表
    public class FieldName
    {
        public const string Id = "id";
        public const string Name = "name";
    }    #region IRecordModify 成员    Dictionary<string, object> IRecordModify.getParameter()
    {
        Dictionary<string, object> dList = new Dictionary<string, object>();
        dList.Add(FieldName.Id, this.Id);
        dList.Add(FieldName.Name, this.Name);
        return dList;
    }    string IRecordModify.getTableName()
    {
        return TableName;
    }    #endregion
}//与数据表Record有关联的Relations数据表实体类
public class Relations : RecordBase 
{
    private decimal? _Id;    public Relations() { }
    public Relations(IDataReader dr) : base(dr) { }
    public Relations(Dictionary<string, object> recordValue) : base(recordValue) { }    public const string TableName = "Relations";
    public class FieldName
    {
        public const string Id = "id";
    }
}
结贴后我会在我的blog里http://blog.csdn.net/oyiboy再开个贴,欢迎大家到时去讨论

解决方案 »

  1.   

    受PETSHOP的影响太大了现在才知道还能这么写实体类受教!!受教!!!
      

  2.   

    太绕了。还是取代不了代码生成器生成的代码。getValue 用字段名取值,性能不如微软的用字段索引取值的快。而且返回的是object,大部分情况下还得再转化一下。
      

  3.   

    我有个疑问, Dictionary<>类好像并没有实现IList 接口,这样你的value是不能直接绑定的,楼住是怎么处理这块的呢?
      

  4.   

    先说一下Dictionary <>是可以直接绑定的,当然如果你说的绑定是指DataSource,DataBind()的话。我上面实现的是一个实体类,也就是单行记录,你也看到我的案例的,我用的是List<>也作的列表,所以,绑定上是没问题的。可能是我没理解你说的绑定是哪种绑定。
      

  5.   

    public class FieldName如果说这个类仅仅用于保存Field名称.那么,是不是可以用来记录字段类型无论是用特性或是用属性且先不论这个名称或是类型去从哪里得到.至少行到类型之后,可以调用IDataReader相应的重载来得到字段值
      

  6.   


    实体类本身的字段数据类型就是和数据表字段数据类型是对应的,何必多此一举去再作记录呢?而且Dictionary<string, object>的作用不只是作为一个离线IDataReader,还有一个作用是关联数据表的数据传递。
      

  7.   

    呵呵,多此一举的在 getvalue返回的是一个object 然后再由object 返回各个类型你直接用getint,getstring,getdate之类的不就完了?
      

  8.   


    首先,IDataReader只会在RecordBase(IDataReader dr)中存活。
    再者,在getvalue时不知道字段类型,甚至是IDataReader里的字段对应实体类哪个字段都不知道。getFieldValue是在请求相应的字段时才会通过Dictionary <string, object>来读取字段值。不知道你说的getInt之类的,能放在哪。其实我也在考虑这个问题,如果能直接用getInt那再好不过了。
      

  9.   

    其实呢,关于处理关系表方面我这个还是有一定的缺陷的,比如:
    select * from  Record a, Record b where a.id = b.id
    这种情况下就不太好处理了。
      

  10.   

    看看你的类如何获得
    Record r1 = new Record(sqldatareader);
    这点大家都是这么做的
    下面看一下,我用自动工具(codesmith)生成的类 
    using (SqlDataReader rdr = SqlHelper.ExecuteReader(SqlHelper.ConnectionStringLocalTransaction, CommandType.Text, SQL_GET_USERPRODUCT , parameters))
                {
                    while (rdr.Read())
                    {
                        product.ProductId = rdr.GetString(0);
                        product.UserName = rdr.GetString(1);
                        product.CompanyID = rdr.GetInt32(2);
                        product.CategoryId = rdr.GetString(3);
                        product.Name = rdr.GetString(4);
                        product.Descn = rdr.GetString(5);
                        product.Image = rdr.GetString(6);
                        product.ImageSmall = rdr.GetString(7);
                        product.Keyword = rdr.GetString(8);
                        product.PlaceOfOrigin = rdr.GetString(9);
                        product.BrandName = rdr.GetString(10);
                        product.Payment = rdr.GetString(11);
                        product.UnitPrice = rdr.GetString(12);
                        product.Packing = rdr.GetString(13);
                        product.PaymentTerms = rdr.GetString(14);
                        product.PaymentRe = rdr.GetString(15);
                        product.DeliveryTime = rdr.GetString(16);
                        product.MinOrder = rdr.GetString(17);
                        product.SupplyAbility = rdr.GetString(18);
                        product.Certification = rdr.GetString(19);
                        product.AddTime = rdr.GetDateTime(20);
                        product.EditTime = rdr.GetDateTime(21);
                        product.ProductState = rdr.GetInt32(22);
                        product.Model = rdr.GetString(23);
                        product.Currency = rdr.GetString(24);
                        products.Add(product);
                    }
                }它对应的CodeSmith模版
             using (SqlDataReader rdr = SqlHelper.ExecuteReader(SqlHelper.ConnectionStringLocalTransaction, CommandType.Text, SQL_SELECT_<%=SourceTable.Name%>, parameters))
                {
                    while (rdr.Read())
                    {
    <%for(int i=0;i<SourceTable.Columns.Count;i++){%>
    <%=SourceTable.Name.ToLower()%>.<%=SourceTable.Columns[i].Name%> = rdr.<%=GetReaderConvert(SourceTable.Columns[i])%>(<%=i%>);
    <%}%>
                    }
                }public string GetReaderConvert(ColumnSchema column)
    {
        if (column.Name.EndsWith("TypeCode")) return column.Name;
        switch (column.DataType)
        {
            case DbType.AnsiString: return "GetString";
            case DbType.AnsiStringFixedLength: return "GetString";
            case DbType.Binary: return "GetByte";
            case DbType.Boolean: return "GetBoolean";
            case DbType.Byte: return "GetByte";
            case DbType.Currency: return "GetDecimal";
            case DbType.Date: return "GetDateTime";
            case DbType.DateTime: return "GetDateTime";
            case DbType.Decimal: return "GetDecimal";
            case DbType.Double: return "GetDouble";
            case DbType.Guid: return "GetString";
            case DbType.Int16: return "GetInt16";
            case DbType.Int32: return "GetInt32";
            case DbType.Int64: return "GetInt64";
            case DbType.Object: return "GetString";
            case DbType.SByte: return "GetByte";
            case DbType.Single: return "GetInt32";
            case DbType.String: return "GetString";
            case DbType.StringFixedLength: return "GetString";
            case DbType.Time: return "GetDateTime";
            case DbType.UInt16: return "GetInt16";
            case DbType.UInt32: return "GetInt32";
            case DbType.UInt64: return "GetInt64";
            case DbType.VarNumeric: return "GetDecimal";
            default:
            {
                return "GetString";
            }
        }
    }而你文中所写先构造一个字典,再从字典中取值
     public RecordBase(IDataReader dr)
        {
            this.recordValue = new Dictionary<string, object>(dr.FieldCount);
            for (int x = 0; x < dr.FieldCount; x++)
            {
                this.recordValue.Add(dr.GetName(x), dr.GetValue(x));
            }
        }
    其实你这个操作是可行,但是,转来转去的,增加了复杂程度,及系统开销,包括一些不必要解箱,封箱,学名叫inbox还是outbox的
    你可以查一下解箱,封箱是个什么意思,简单的说就是复杂类型和简单类型相互转换而增加的开销
     并且你下面这个方法是错的,你试着给ID一个值之后再取id,能取到么?
        public decimal Id
        {
            get { return this.getFieldValue(ref _id, FieldName.Id); }
            set { _id = value; }
        }不过看你的文章,发现你对IDataReader理解也有误解,IDataReader不能离线,
    它必须是独占Connection的.不过你处理的很好,把值存到字典中没直接取IDataReader实体类的生成,还是借助工具比较好.
      

  11.   


    dr.GetValue(x)返回的类型本身就是object,所以不存在装箱过程。最多是在this.getFieldValue时拆箱而以。
    请选看一下getFieldValue的重载方法。    //获取字段数据类型为 decimal 的字段值
        private decimal getFieldValue(ref decimal? properties, string fieldName)
        {
            if (!properties.HasValue) { properties = (decimal)(this.getValue(fieldName) ?? 0m); }
            return properties.GetValueOrDefault();
        }当_id有值时会跳过赋值,直接返回_id的值,不存在不能取的情况。我没说IDataReader能离线,我是说相当于一个离线的IDataReader,如果我理解IDataReader能离线的话,还费这么大劲用字典来装数据干什么。关于这段代码,只能适用于一句sql吧。或是说只能适用于一个数据集。如果字段顺序被打乱了的话。那么你rdr.GetString(0);得到的数据就不是你所想得到的字段值了。比如说,我只需要UserName和ProductId及Name这三个字段时间,是不是需用再次重作一段这个代码呢?
    关于你的这段我倒是觉得有点多余。get类型应该看实体类的具体类型,如果我来作你这段的话,我用一个重载来作,如:    public decimal getFieldValue(decimal field, IDataReader dr, int number)
        {
            return dr.GetDecimal(number);
        }
        public string getFieldValue(string field, IDataReader dr, int number)
        {
            return dr.GetString(number);
        }
    ......
            using (SqlDataReader rdr = SqlHelper.ExecuteReader(SqlHelper.ConnectionStringLocalTransaction, CommandType.Text, SQL_SELECT_ <%=SourceTable.Name%>, parameters)) 
                { 
                    while (rdr.Read()) 
                    { 
    <%for(int i=0;i <SourceTable.Columns.Count;i++){%> 
    <%=SourceTable.Name.ToLower()%>. <%=SourceTable.Columns[i].Name%> = getFieldValue(<%=SourceTable.Name.ToLower()%>. <%=SourceTable.Columns[i].Name%>, rdr, <%=i%>)
    <%}%> 
                    } 
                } 
      

  12.   

    上文表字段改变索引改变,类型改变,这点,是属实的,
    但是应用中,很少在运行期问去更改表结构如果的确存在字段不确定的时候,我会采用列转行的形式上文中不存在装箱.如果你仅仅你认为  object getvalue,那么,
    所有的类型均可以用object表示,你的意思可以不可以均理解为这类转换均不需要装箱
    不过在转换方面你是我认识中最能转的了比如这一段product.ProductId = rdr.GetString(0);你的想法就很奇特,非要去得到一个string fieldname
    再根据fieldname去查到fieldindex,再去根据fieldindex去getstring或index public string getFieldValue(string field, IDataReader dr, int number)
     {
               return dr.GetString(number);
      }
      

  13.   

    所以你写出这么能绕的程序我能理解..
    何况程序员多多少少有一点偏执,意见归意见,不能接受也不多讲你贴上来的程序好像也不是很合语法
     if (!properties.HasValue) { properties = (decimal)(this.getValue(fieldName) ?? 0m); }this.getValue(fieldName) ?? 0m像三目吧,又不太像..
    我就没细看,至于存到哪里去了,我确实是不知道
      

  14.   

    我理解是值类型到object转换才算是装箱,而object到值类型的转换才算是拆箱,而object 到object 的赋值不存在装箱的过程。
    不好意思,我实在不知道你说的是哪。我只是以fieldname为key去获取Dictionary<string, object>内的值而以。

    其实呢,我的那段代码只是我自己琢磨的,目前也是正在使用中的代码,如果你能提出哪段代码可以省掉的话,我是非常乐意去掉的。当然是不影响使用的情况下。我是个比较懒的人,象你作的那种方式,如果修改数据集(注意,是数据集,不是数据表)就需要对代码进行修改的话,会让我很郁闷的,所以,我才会想过用fieldname来作key获取数据集的值,这样,不管sql返回的数据集是什么,实体类生成部份的代码我是不用动的。只需要作出相应的查询句,实体类自身就可以作出相应的操作。虽然表结构不会变,但是数据集结构会变呢,毕竟有一些客户的要求比较变态的,某块数据列表,时不时的要加个减几个字段,如果象你那样作的话,估计我光维护这个实体类构建部份就有够头疼一段时间了。如:原来只需要ProductId,UserName,突然要加一个Name什么的,就痛苦了。关于this.getValue(fieldName) ?? 0m ,不知道你的疑问是什么,??的作用是当左边值为null时返回右边值,返之返回左边值。
    还有,在赋值之前的if (!properties.HasValue) 就是判断_id是否有值。所以,当你给_id赋于非null的值时会跳过对_id的赋值。
    只是,不知道你有没接触过"decimal?"之类的数据类型。
      

  15.   

    Mark...!!!终于看到传说中的实体类了。
      

  16.   

    事实上,我个人感觉不怎么样。1 编译时,如果字段名称是AAA,结果写成了AAB,编译不出错。2 编译时,使用这个实体类的人,时时需要关心,数据类型,有大量的拆箱的过去,如果类型错误,编译也不出错。所以我感觉这样的实体类,分大大分散开发人员的注意力,而这些注意力应该被使用到业务逻辑处理上面的。建议楼主看一下LINQ产生的实体类。
    一家之言,不当之处,请开心一笑。
      

  17.   


    程序debug不能全靠编译的呢。关于LINQ是3.0的,目前我还在用2.0,所以没接触过,有时间一定仔细看看,看有什么可以借鉴的。
      

  18.   

    楼主
    你是否觉得你的dictionary<string,object>是神来之笔吧
      

  19.   


    不是神来之笔,是无奈之举,因为不能即时识别字段类型,也不能识别哪个数据字段对应实体类字段。更不知道数据集内是否有关联表数据。所以只好用字典来处理这些东西。其实我一直想取缔掉字典,但没想到有什么更好的办法来处理数据字段到实体类字段的识别方式。当然,你的那个getString也是不错的选择,不过,对于我来说不太适用,因为我的数据库string不只是单纯的string,还可能是enum,或是数组之类的。
      

  20.   

    其实大家并没有说你的方法不对,只有说有改良的余地,
    就是绕来绕去的,把所有的类型先转为object,再还原为它的基类型其实呢,对类型进行判别再调用相应的Get<类型>(索引)
     如getint16(i) getstring(i) getbyte(i)
    在MSDN中是认同的这样的操作的
    比如我们在MSDN中可以看到这样一段
    http://msdn.microsoft.com/zh-cn/library/system.data.idatarecord.getfieldtype.aspx

    通过指示要调用的强类型访问器,此信息可用于提高性能。(例如,使用 GetInt32 比使用 GetValue 快大约十倍。)
    如果你想在运行时判定字段类型,有很多种方法,如反射
     private object getValue(string field)
        {        Type t = this.GetType();
            FieldInfo fi = t.GetField(fieldname, BindingFlags.NonPublic | BindingFlags.Instance);
            if (fi.FieldType == typeof(string))
            {
     //你的处理方法
            }
            else if (fi.FieldType == typeof(int))
            {
            }
            object value = null;
            /*
             * 这里之所有用try而不用recordValue.ContainsKey来判断字段是否存在,
             * 是因为字段不存在这种错误是不应该出现的,如果出现那就是查询句或是别的地方有错。
             * 所以不需要用recordValue.ContainsKey来跳出找不到字段的错误,
             * 而用try来抛出错误的话,可以更好的提示程序员出错是什么原因。
             */
            try
            {
                
                //字段值为DBNull时返回null,让getFieldValue作适当的处理。
                if (!(recordValue[field] is DBNull)) { value = recordValue[field]; }
            }
            catch (Exception e) { throw new Exception(string.Format("找不以字段{0}", name)); }
            return value;
        }如果你认为上面的只有类型太少了,你还可以自定[AttributeUsage(AttributeTargets.Field)]
    public class FieldTypeAttribute :System.Attribute 
    {
        string fieldtype;
        int length
        public FieldTypeAttribute(string ft,int length)
    {
            this.fieldtype = ft;
    this.length = length;
    }
        public string FieldType
        {
            get
            {
                return fieldtype;
            }
        }
    public length
    {
    get{return this.length;}
    }
    }//标记
    [FieldType("nvarchar",80)]
    private string productname ;
    //获取信息
     Type t = this.GetType();
     FieldInfo fi = t.GetField(fieldname, BindingFlags.NonPublic | BindingFlags.Instance);
    if (fi.IsDefined(typeof(FieldTypeAttribute), false))
    {
     FieldTypeAttribute  fta  = fi.GetCustomAttributes(typeof(FieldTypeAttribute), false)[0] as FieldTypeAttribute;//fta.FieldType
    //fta.length}
    可以负责任的告诉你. 3.5中的实体类.也是采用特性来得到这些信息的
      

  21.   



    我也没说什么对与不对,我只是解释为什么我要这么作而以,之所以共享出来,寻求更好的改良方案,是我首要目的。
    其实呢,要解决拆装箱的问题也不是不能解决,大不了把dictionary <string,object>的object作成一个struct,然后由struct来作对应类型重载来处理就成了。只是这样是否会比GetValue及拆箱的代价还要大呢?对于反射,“消耗极大,能少用就少用",这是我刚开始写C#时看到的,所以,直到现在我都是谈“反射”色变,如果用反射的话,我的实体类会更为简单一些。只是反射的代价和GetValue及拆箱比哪个更好取舍一些呢?还有关于特性(sdk里好象说的是属性),这个东西也用到反射操作。理由同上。如果反射不是我所理解的那样消耗极大的话,那我的count table和class fieldName就没有存在的理由了。直接用特性来标记,这看上去代码更为简洁,而且在某些继承或是接口方面的操作也比较方面。
      

  22.   

    如果你用了linq to sql产生的实体类,我想,你恐怕也会象我一样,把自己做的轮子丢到一个什么人都看不到的地方去。
      

  23.   

    其实,实体类的最大意义就在于:可以通过工具生成实体类(他人或自己写的工具)
    对于表结构改变的而产生维护实体类的问题,完全可以通过工具重新覆盖一遍来解决。另外,普遍的认识中,认为DataReader读取的速度比DataTable来得快(实际上,2.0以后的DataTable的性能大大提高了),但问题在于
    1.DataReader中,数据格式为Object,当对字段属性赋值时,仍然需要大量的转换。
    2.数据库字段中,除了Null之外,还存在空的情况。
    由于需要大量转换,导致读取快的优点被大量抵消。早期的时候,我处理数据也是使用DataReader来进行,后来发现,使用DataTable获取数据(2.0后),根本不比前者慢,而且由于得到的数据明确都是string类型,即使数据库中为NULL或空,都能够通过dt["xxx"].ToString()方式获得非空的""(空串),数据的判断由此变得单一。
    改变取值方式之后,经过测试,两者之间的速度不相上下。另外,ORM最大的问题在于如何处理关系类。很多工具使用了各式的方法来达成这个目的,但几乎都造成复杂度大大提高的后果。
    但这个问题,如果通过对数据库表与字段严格的命名方式来解决,就变得非常的轻松愉快了。如果你设计实体类并不想变成一个公用的东西,而只是解决自己的问题在自己的项目或团队中使用,那么,我建议你使用对表设计的规范来解决。比如
    asm_branch表
    字段:brc_id
    asm_user表
    相关字段:usr_brc_id
    如上:在我们的定义中,usr_brc_id字段中的后6位字母,即是关联到某个表主键为该6位字母的表(前提是所有的字段不可重名),在这种情况下,实现实体类的关联,轻而易举。
    再有,对于增、删、改操作,当然可以使用某些接口或反射之类的方法来实现,代码会少很多,但同时性能也会降低很多。
    如果你有自己的实体类生成器,那么,生成实体类根本就是不需要费心的事情,这种情况下,值得为了减少一点代码而牺牲性能吗?不需要,当表字段变化时,无非是再生成一次罢了。
      

  24.   

    用linq to entity,VS自动生成 多简单...