把Sql数据转换为业务数据的几种方法

ORM系统必须把数据库中的数据转换为业务数据,转换的方法大致有3种,本文就试图对它们做一些简单分析。

1、属性反射。就是通过反射,获取业务实体类的各个属性,然后再设置这些属性的值。这个方法最简单、最稳定、通用性最强、可维护性最强、性能最差。例如NHibernate就是用这种方法实现的,它通过IGetterISetter接口实现对某个业务实体类属性的读取和写入。DongLiORM的早期版本也是用的这种方法,他通过BusinessObject的属性索引器实现。其原理就是首先通过获取某个业务实体类某个属性的PropertyInfo,然后调用该PropertyInfoGetValue或者SetValue方法。类似的代码如下:

PropertyInfo info = BusinessType.GetProperty(PropertyName);

info.SetValue(this, value, null);

比如,现在有一个业务实体类UserItem,其定义如下:

public class UserItem

     {

        public UserItem() { }

        private string _userid;

        private string _username;

        private string _pwd;

        private string _email;

        public string UserID

        {

            set { _userid = value; }

            get { return _userid; }

        }

        public string UserName

        {

            set { _username = value; }

            get { return _username; }

        }

        public string Pwd

        {

            set { _pwd = value; }

            get { return _pwd; }

        }

        public string Email

        {

            set { _email = value; }

            get { return _email; }

   }

}

UserItem对应数据库中的Users表,他的属性对应Users表的字段UserIDUserNamePwdEmail。现有一个返回了Users表数据的IDataReader,要把IDataReader转换为一个UserItem的数组,就用类似如下的方法:

        public T[] DbReaderToBusinessObject<T>(IDataReader Reader) where T: new()

        {

            List<T> list = new List<T>();           

            while (Reader.Read())

            {

                T Item = new T();               

                Type BusinessType = Item.GetType();

                for (int i = 0; i < Reader.FieldCount; i++)

                {

                    string PropertyName = Reader.GetName(i);

                    PropertyInfo info = BusinessType.GetProperty(PropertyName);

                    info.SetValue(Item,Reader[i],null);

                }

                list.Add(Item);

            }

            Reader.Close();

            return list.ToArray();

  }

2、通过XML序列化。就是在读取数据的时候,从数据库中返回的Xml形式的数据,然后通过用XmlSerializerDeserialize方法把数据转换成业务数据。目前没有见到有用这种方法实现的产品。其优点同第一种方法,但转换的效率似乎比第一种方法效率高一些(不过也不会高多少,反序列化实际上也需要反射),但是增加了从数据库中返回数据的数据量,而且对于已经编译了的业务实体类也无能为力。其实现方法类似下面的过程(以Sql Server 2000为例)。

   业务实体类的定义:

   [Serializable]

   [XmlRoot("Users")]

    public class UserItem

    {

        public UserItem(){}

        private string _userid;

        private string _username;

        private string _pwd;

        private string _email;

        [XmlAttribute("UserID")]

        public string UserID

        {

            set { _userid = value; }

            get { return _userid; }

        }

        [XmlAttribute("UserName")]

        public string UserName

        {

            set { _username = value; }

            get { return _username; }

        }

        [XmlAttribute("Pwd")]

        public string Pwd

        {

            set { _pwd = value; }

            get { return _pwd; }

        }     

        [XmlAttribute("Email")]

        public string Email

        {

            set { _email = value; }

            get { return _email; }

        }

   }

 

   转换过程:

   List<UserItem> UserList = new List<UserItem>();

   string XmlSql = "select * from Users for xml auto";

    SqlCommand cmd = new SqlCommand(XmlSql, con);

  cmd.CommandText = XmlSql;

    XmlReader r = cmd.ExecuteXmlReader();

    XmlSerializer sr = new XmlSerializer(typeof(UserItem));

    while (r.Read())

    {

        UserList.Add(sr.Deserialize(r) as UserItem);

    }

r.Close();

 

3、在业务实体类中自定义一个把Sql数据解析成业务数据的方法。这种方法的优点是转换的效率非常高,在理想的情况下,和Ado.net的速度不相上下,缺点是必须对每个业务实体类实现一个把Sql数据解析成业务数据的方法。手工编码的工作量非常大,配上专门的代码辅助工具,可以减轻工作量,但是,如果数据结构需要改动,则改动的地方比较多,容易出错。用这种方法实现的有NBear V3DongLiORM 1.2等。比如NBear V3中就有几个专门方法,比如:

public override void SetPropertyValues(System.Data.IDataReader reader);

public override void SetPropertyValues(System.Data.DataRow row);

 

DongLiORM则是通过重写索引器(还是上文的UserItem)

public override object this[string PropertyName]

     {

            get

            {

                switch (PropertyName)

                {

                    case "UserID": return _userid;

                    case "UserName": return _username;

                    case "Pwd": return _pwd;

                    case "Email": return _email;

                }

                return base[PropertyName];

            }

            set

            {

                switch (PropertyName)

                {

                    case "UserID": _userid = (string)value; return;

                    case "UserName": _username = (string)value; return;

                    case "Pwd": _pwd = (string)value; return;

                    case "Email": _email = (string)value; return;

                }

                base[PropertyName] = value;

            }

}

 

“鱼我所欲也,熊掌亦我所欲也,二者不可得兼,舍鱼而取熊掌者也”。系统的性能和代码简单性,二者不可兼得,到底我们更需要谁呢?

 

狂想:假设我们有一个这样的东西,他能够在运行时候改变现有的某个类(比如UserItem)的某个属性(比如索引器)/方法(比如SetPropertyValues)就好了,这样我们就可以在设计的时候写非常少的代码,而又能够获得比较高的运行效率。可惜PropertyBuilder等只能对动态类有用,帮不上忙。

 

我所想到方法的就这些,各有优点和缺点,不知各位有没有其他的方法,能让统的性能和代码简单性和谐统一。

posted @ 2007-07-27 08:00 永红 阅读(2724) 评论(22)  编辑 收藏 网摘 所属分类: .Net

  回复  引用  查看    
#1楼 2007-07-27 08:09 | 白菜园      
有没有试过用DynamicMethod来获取与设置实体属性值
  回复  引用  查看    
#2楼 2007-07-27 08:18 | 大石头      
天哪,第三种方法居然和我的做法一模一样,我可从来没听说过DongLiORM。

我在实体基类里面写一个索引器,使用反射实现,实体类就用代码生成器生成,和方法三一样。这样做,即使实体类没有继承这个索引器,基类也能通过反射实现。
  回复  引用  查看    
#3楼 [楼主]2007-07-27 08:21 | 永红      
TO 白菜园:没有试过。谢谢提供了新的方法。

TO 大石头:这样做,即使实体类没有继承这个索引器,基类也能通过反射实现。
-----------------------------------------------------
DongLiORM也是这样的。不过,实体类不继承这个索引器,那么的性能将会大打折扣,相差至少2倍。
  回复  引用  查看    
#4楼 2007-07-27 08:26 | henry      
我所了解NHibernate应该使用DynamicMethod的委托实现设置和获取属性值,这种效率已经接近直接硬编码.在jit中无论是硬编码还是后期生成效率应该差别不大.很多基本泛型的实例应该也是在后期生成的.
  回复  引用  查看    
#5楼 [楼主]2007-07-27 08:40 | 永红      
To henry:
我所了解的NHibernate确实是通过PropertyInfo.SetValue来工作的。他首先加载所有的属性,然后,在需要设置值的时候,就调用ISetter.Set。下面是用Reflector产生的代码:
public void Set(object target, object value)
{
try
{
this.property.SetValue(target, value, new object[0]);
//就是在这里设置业务实体类的属性的值的。
}
catch (ArgumentException ae)
{
if (!this.property.PropertyType.IsAssignableFrom(value.GetType()))
{
string msg = string.Format("The type {0} can not be assigned to a property of type {1}", value.GetType().ToString(), this.property.PropertyType.ToString());
throw new PropertyAccessException(ae, msg, true, this.clazz, this.propertyName);
}
throw new PropertyAccessException(ae, "ArgumentException while setting the property value by reflection", true, this.clazz, this.propertyName);
}
catch (Exception e)
{
throw new PropertyAccessException(e, "could not set a property value by reflection", true, this.clazz, this.propertyName);
}
}



  回复  引用  查看    
#6楼 2007-07-27 09:05 | henry      
?奇怪上次程序报错误提示属性设置错误提示没有StringHandler...
你的是什么版本?我在1.2.0.2001怎么也找不到你所说的代码.

  回复  引用  查看    
#7楼 [楼主]2007-07-27 09:20 | 永红      
我的是1.2.0.4000,也许是版本不同吧。
  回复  引用  查看    
#8楼 2007-07-27 09:24 | jjx      
@永红
不清楚你是那个版本,说说现在nhibernate的做法吧

当nhibernate得到一个IDataReader 时,他最终会调用NHibernate.Persister.Entity.AbstractEntityPersister.SetPropertyValues,而abstractEntityPersister会根据hibernate.use_reflection_optimizer 的设定确定是否使用IReflectionOptimizer接口实现,默认的,现在hibernate.use_reflection_optimizer是true的,而且字节码提供者默认被设置为 lce,即NHibernate.Bytecode.Lightweight
至于NHibernate.Bytecode.Lightweight.ReflectionOptimizer 做了什么,大家应该都猜得到
  回复  引用  查看    
#9楼 [楼主]2007-07-27 09:39 | 永红      
谢谢jjx,我的原意并不是否定NHibernate,也许是我对其代码看的不是很仔细,对NHibernate的实现机制理解有误。

我写本文的目的是通过本文找到一些能能够把Sql数据转换成业务数据的其他更好的办法。

再次谢谢大家。
  回复  引用  查看    
#10楼 2007-07-27 09:45 | henry      
其实现在基本都会用Emit或CodeDom,这样就不会给Entity的定义带来多余的工作.其实这两种做得好基本和你硬编码的效率差不多,不过损耗是会有一点点的,但这一点点实在是太少了.
  回复  引用  查看    
#11楼 2007-07-27 10:03 | 随心所欲      

我用的ORM用的是第1和3种方法。
对于效率,我这么看:设计好你的逻辑,不要一次取出过多数据(多了就分页或者加入更多Filter),几十条数据所带来的性能影响就会非常小。
做好我们的逻辑(清晰,快速),剩下的让硬件去搞定吧。
  回复  引用  查看    
#12楼 [楼主]2007-07-27 10:14 | 永红      
To 随心所欲:
几十条数据所带来的性能影响就会非常小
--------------------------------------
不过有时我们需要的数据远远不止几十条。比如对某年度的库存记录做一次盘点工作。当然我们可以用数据库的存储过程来做,但是完了以后,还是需要取出来的,这时的数据量还是非常的大。
  回复  引用  查看    
#13楼 2007-07-27 10:48 | 随心所欲      
@永红
这种工作(还有报表)俺都是绝不用ORM的。
对于数据量大的处理,用后台任务,慢慢跑,不影响用户使用。
对于报表,俺建议还是写复杂的sql,ORM太束手。
总之,ORM的长处在于快速开发常用的繁琐的CRUD,而不是细微和处理大数据。
扬长避短。
  回复  引用  查看    
#14楼 2007-07-27 10:51 | 随心所欲      

另 :
sql server 2008里面好像有个类似的功能,可以把数据表对象化。
没有仔细看过,不知道这是不是MS的ORM,实现方法好像是用的XML。
  回复  引用  查看    
#15楼 [楼主]2007-07-27 11:03 | 永红      
@ 随心所欲,
对大量的数据,目前我也是这样做的,但又想用到ORM实体类所带来的方便,所以才提出这个话题。
Sql server 2008还没有用过。找个时间看看。

  回复  引用  查看    
#16楼 2007-07-27 11:18 | 随心所欲      
@永红
任何工具都有适用性,不可强求的,ORM也不例外。
  回复  引用  查看    
#17楼 [楼主]2007-07-27 11:29 | 永红      
看到DynamicMethod,也许这是一个好的解决途径。
  回复  引用  查看    
#18楼 2007-07-27 14:32 | 双鱼座      
楼主没看过我的文章?http://www.cnblogs.com/Barton131420/archive/2005/11/09/272647.html
该文是针对Kanas.net 1.3.2的,适用于.Net Framework 1.1,所以采用的是Emit,而不是DynamicMethod。当然,我的框架的2.0的实现有所变化,性能有了更进一步的提高。不过我正在开发的是3.0,已经解决了我[http://www.cnblogs.com/Barton131420/archive/2007/01/07/613955.html]一文中提到的new业务对象时的性能问题,完全打乱了通常意义上的类的布局模式。Kanas.net从2.0开始,业务类的布局方式已经抽象为接口,可以采用各种灵活的方式来实现。
  回复  引用  查看    
#19楼 [楼主]2007-07-27 15:15 | 永红      
@双鱼座
对于使用数据囊技术,加重了业务实体类的定义。希望能够找到一种在设计时不加重业务实体类的负担,而又能够在运行时有较高效率的方法。DynamicMethod/Emit应该是一个不错的选择。

谢谢各位。
  回复  引用  查看    
#20楼 2007-07-27 18:14 | 镜涛      
还在学习阶段!
  回复  引用  查看    
#21楼 [楼主]2007-08-02 08:23 | 永红      
@ 镜涛
谢谢。大家一起努力。

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-07-27 08:04 编辑过
Google站内搜索

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》



相关文章:

相关链接: