没有ORM或代码生成数据就不能持久化了? - 用范型技术代替代码生成!
//一些唠叨
现如今,写ORM的人越来越多,写代码生成工具的也是一波又一波,其实仔细去看,凡是ORM的,大多数实现原理其实都大同小异,重复来重复去有何意义呢?写代码生成工具的,不知道自己是不是相信会有很多人用你的工具来开发实际的应用呢?我真的要开发程序,要生成也自己写了,有钱的,或者用盗版的可以用codesmith(还有cavingdeep的开源工具DCG),没钱的可以用velocity。。。自己写一个小程序的时间不会比提心吊胆用别人的工具来得昂贵多少。再说了,能自己写这类辅助程序,也应该是每个合格的程序员必备的素质,需要用别人的工具来生成,还是生成“三层”的代码吗?如果是让您来主持一个项目,你会这样来写程序吗?
我这么说不是要说ORM不好,不是要说写ORM框架的朋友不善良,不是要说代码生成不重要,也不是要说些代码生成工具的朋友不勤劳。相反,我是非常尊重所有愿意写工具给大家用,或者将自己的思想share出来的朋友的。许多工具也确实能帮助我们提高很大的开发效率。我只是希望,更多重新写的ORM或代码生成工具,能够真正的有创新,或者易用性,或者执行效率,或者其他什么重要的方面有很大程度的改进,而不仅仅是重复百分之八九十别人已经做了的工作。如果重写改进不大,反而降低了稳定性,也许在别人的框架上进行改进要比重写绝大多数方面都来得更合理和经济。
-------------------------------------------------------------------------
摘要:在本文中,Teddy将在对C#2.0中的范型编程的理解的基础上,列举一个应用范型技术以改善以前一些通常只能通过代码生成方式实现的软件构架的实践。文中,Teddy将给出利用范型技术重写一个自己原来基于代码生成思想所写的持久化层方案。新的范型方案既不依赖ORM,也不依赖代码生成,只需定义需要的数据库结构和实体类的基本结构(当然,如果还想偷懒,也完全可以写个简单程序生成实体类)就能够获得非常方便的持久化支持。为了改善程序的执行效率,程序中甚至完全避免了使用可能会影响执行效率的反射,并且充分在可能的地方充分运用了Cache模式以避免不必要的性能损失。
1 实例
就让我们从改进后的范型方案的一个最简单、直观的使用实例开始!
例如:我们有一个数据库表About(ID,Title,Content,Deletable,Order),这个表我只是随便取自以前的一个程序,所以,读者不用关心字段的实际含义。然后,为了对这张表中的数据进行操作,我们定义一个实体类:
一个实体类必须继承自实体类的基类Entity<EntityType>。注意,这是一个带来性参数的范型基类,在没有范型的情形下,我以前为了给实体类附加公共的操作,只有两种选择:一,要么为每个类自动生成一份包含公共操作的代码,这样的话代码累赘得很,因为代码复杂,也只能用工具生成,要自定义一些实体类的细节几乎不可能;二,定义一个公共基类,但是,此时,公共的操作只有通过反射获取继承类的具体化信息,如果公共操作要返回和具体的实体类实例,也只能返回object,由用户自己去类型转换。两种方案都不是很优雅。而在范型方案下,却是那样的简单明了。可以很高兴得告诉你,除了定义数据库表结构和上面这个实体类,您不需要写任何代码,就已经获得了持久能力了。当然,在您的程序的web.config或者.exe.config中,定义ConnectionString是必不可少的。只需象下面这样定义.exe.config:
上面是MsAccess的定义方式,如果是SqlServer可以象下面这样定义(目前该组件只提供了SqlServer和MsAccess数据库Provider,以后可以方便地扩展更多的):
以上演示的是由SimpleDataAccess<EntityType>这个范型类定义的最常用最简单的数据库操作,如果需要更复杂的操作,可以访问另一个类SimpleDbHelper,如果需要比SimpleDnHelper类更灵活复杂的数据库操作,可以直接访问Database类,这里我就不演示了,感兴趣的朋友可以自己尝试。是不是感觉非常简单呢?如果需要事务支持,在.Net2.0下时非常简单的,只需要引用System.Transactions命名空间的类就行,可以参照MSDN文档,我这里就不演示了。
以上的演示中值得注意的是,SimpleDataAccess<EntityType>这个范型类同样代替了原来我为每个实体类生成的一个DataAccess类,因为现在利用范型的缘故,只需要定义一个公共的类,调用时动态邦定About到EntityType参数就相当于以前的一个独立的类AboutAccess了。实在是很大的简化。具体的大家可以自己下载示例代码。
可见,凡是用到范型模版参数的类,都能代替一批类,从而大大简化整体的软件构架,个人觉得这是范型带来的最大的好处,能够极大的较少以前对代码生成的依赖。当然,同时,范型也带来了许多以前不能使用的语法,特别是继承体系的新的组织方式。最典型的语法如:public class ClassA<T> where T : ClassA<T>, new() { ... }这样的类定义。这个本人也在持续研究中,非常有意思,有机会再和大家独立出来探讨。
2 下载
Helper.TestApp.zip
3 关于下载文件的说明
1)以上zip包未包含持久化组件的源码(抱歉~~,以后可能会公开提供源码,不过如果你反编译的话我也不介意^-^);
2)zip包中包含了持久化组件Helper.Data.dll, Helper.Data.dll.SqlServer.dll, Helper.Data.MsAccess.dll的程序集(见Helper.TestApp\bin\Debug目录),可以直接引用;
3)Helper.TestApp\db目录包含了演示用的MsAccess数据库以及SqlServer数据库脚本,演示程序默认操作dbzzz.mdb数据库,如果需要尝试操作SqlServer,可以参照上面的config格式修改为SqlServer方式,并在SqlServer中建一个数据库并运行sql_createtable.sql脚本;
4)zip包中包含了演示程序源码,需要vs2005以上版本才能打开和编译。
01/04 更新
本文续篇:来一点反射,再来一点Emit —— 极度简化Entity!
现如今,写ORM的人越来越多,写代码生成工具的也是一波又一波,其实仔细去看,凡是ORM的,大多数实现原理其实都大同小异,重复来重复去有何意义呢?写代码生成工具的,不知道自己是不是相信会有很多人用你的工具来开发实际的应用呢?我真的要开发程序,要生成也自己写了,有钱的,或者用盗版的可以用codesmith(还有cavingdeep的开源工具DCG),没钱的可以用velocity。。。自己写一个小程序的时间不会比提心吊胆用别人的工具来得昂贵多少。再说了,能自己写这类辅助程序,也应该是每个合格的程序员必备的素质,需要用别人的工具来生成,还是生成“三层”的代码吗?如果是让您来主持一个项目,你会这样来写程序吗?
我这么说不是要说ORM不好,不是要说写ORM框架的朋友不善良,不是要说代码生成不重要,也不是要说些代码生成工具的朋友不勤劳。相反,我是非常尊重所有愿意写工具给大家用,或者将自己的思想share出来的朋友的。许多工具也确实能帮助我们提高很大的开发效率。我只是希望,更多重新写的ORM或代码生成工具,能够真正的有创新,或者易用性,或者执行效率,或者其他什么重要的方面有很大程度的改进,而不仅仅是重复百分之八九十别人已经做了的工作。如果重写改进不大,反而降低了稳定性,也许在别人的框架上进行改进要比重写绝大多数方面都来得更合理和经济。
-------------------------------------------------------------------------
摘要:在本文中,Teddy将在对C#2.0中的范型编程的理解的基础上,列举一个应用范型技术以改善以前一些通常只能通过代码生成方式实现的软件构架的实践。文中,Teddy将给出利用范型技术重写一个自己原来基于代码生成思想所写的持久化层方案。新的范型方案既不依赖ORM,也不依赖代码生成,只需定义需要的数据库结构和实体类的基本结构(当然,如果还想偷懒,也完全可以写个简单程序生成实体类)就能够获得非常方便的持久化支持。为了改善程序的执行效率,程序中甚至完全避免了使用可能会影响执行效率的反射,并且充分在可能的地方充分运用了Cache模式以避免不必要的性能损失。
1 实例
就让我们从改进后的范型方案的一个最简单、直观的使用实例开始!
例如:我们有一个数据库表About(ID,Title,Content,Deletable,Order),这个表我只是随便取自以前的一个程序,所以,读者不用关心字段的实际含义。然后,为了对这张表中的数据进行操作,我们定义一个实体类:
using System;
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Ilungasoft.Helper.Data.Entity<About>
{
public About()
{
keyValues.Add("ID", DefaultValue<int>());
keyValues.Add("Title", DefaultValue<string>());
keyValues.Add("Content", DefaultValue<string>());
keyValues.Add("Deletable", DefaultValue<bool>());
keyValues.Add("Order", DefaultValue<int>());
}
public int ID
{
get { return (int)keyValues["ID"]; }
set { keyValues["ID"] = value; }
}
public string Title
{
get { return (string)keyValues["Title"]; }
set { keyValues["Title"] = value; }
}
public string Content
{
get { return (string)keyValues["Content"]; }
set { keyValues["Content"] = value; }
}
public bool Deletable
{
get { return (bool)keyValues["Deletable"]; }
set { keyValues["Deletable"] = value; }
}
public int Order
{
get { return (int)keyValues["Order"]; }
set { keyValues["Order"] = value; }
}
}
}
namespace Ilungasoft.Helper.TestApp.DomainObject
{
public class About : Ilungasoft.Helper.Data.Entity<About>
{
public About()
{
keyValues.Add("ID", DefaultValue<int>());
keyValues.Add("Title", DefaultValue<string>());
keyValues.Add("Content", DefaultValue<string>());
keyValues.Add("Deletable", DefaultValue<bool>());
keyValues.Add("Order", DefaultValue<int>());
}
public int ID
{
get { return (int)keyValues["ID"]; }
set { keyValues["ID"] = value; }
}
public string Title
{
get { return (string)keyValues["Title"]; }
set { keyValues["Title"] = value; }
}
public string Content
{
get { return (string)keyValues["Content"]; }
set { keyValues["Content"] = value; }
}
public bool Deletable
{
get { return (bool)keyValues["Deletable"]; }
set { keyValues["Deletable"] = value; }
}
public int Order
{
get { return (int)keyValues["Order"]; }
set { keyValues["Order"] = value; }
}
}
}
一个实体类必须继承自实体类的基类Entity<EntityType>。注意,这是一个带来性参数的范型基类,在没有范型的情形下,我以前为了给实体类附加公共的操作,只有两种选择:一,要么为每个类自动生成一份包含公共操作的代码,这样的话代码累赘得很,因为代码复杂,也只能用工具生成,要自定义一些实体类的细节几乎不可能;二,定义一个公共基类,但是,此时,公共的操作只有通过反射获取继承类的具体化信息,如果公共操作要返回和具体的实体类实例,也只能返回object,由用户自己去类型转换。两种方案都不是很优雅。而在范型方案下,却是那样的简单明了。可以很高兴得告诉你,除了定义数据库表结构和上面这个实体类,您不需要写任何代码,就已经获得了持久能力了。当然,在您的程序的web.config或者.exe.config中,定义ConnectionString是必不可少的。只需象下面这样定义.exe.config:
<configuration>
<connectionStrings>
<add name="DatabaseConnection"
connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Teddy\Helper\Helper.TestApp\db\dbzzz.mdb"
providerName="Helper.Data.MsAccess.dll,Ilungasoft.Helper.Data.MsAccess.AccessDatabase" />
</connectionStrings>
</configuration>
<connectionStrings>
<add name="DatabaseConnection"
connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Teddy\Helper\Helper.TestApp\db\dbzzz.mdb"
providerName="Helper.Data.MsAccess.dll,Ilungasoft.Helper.Data.MsAccess.AccessDatabase" />
</connectionStrings>
</configuration>
上面是MsAccess的定义方式,如果是SqlServer可以象下面这样定义(目前该组件只提供了SqlServer和MsAccess数据库Provider,以后可以方便地扩展更多的):
<configuration>
<connectionStrings>
<add name="DatabaseConnection"
connectionString="Server=(local);Database=TestDb;Uid=sa;Pwd=sa"
providerName="Helper.Data.SqlServer.dll,Ilungasoft.Helper.Data.SqlServer.SqlDatabase" />
</connectionStrings>
</configuration>
<connectionStrings>
<add name="DatabaseConnection"
connectionString="Server=(local);Database=TestDb;Uid=sa;Pwd=sa"
providerName="Helper.Data.SqlServer.dll,Ilungasoft.Helper.Data.SqlServer.SqlDatabase" />
</connectionStrings>
</configuration>
好了,现在一切需要手工写的代码和配置都完成了。下面就只需要使用代码了,看看最简单的典型的持久化方法调用示例:
SimpleDataAccess<About>.Insert(new About().GetMemberNames("ID"), new About().GetMemberValues("ID")); //插入一条About记录,要插入About实例除了ID字段之外的所有字段值
SimpleDataAccess<About>.Delete(new Condition("ID = @id", 100)); //删除id = 100的纪录
About[] abouts = SimpleDataAccess<About>.Select(null, null); //获取所有记录,按照默认的排序
SimpleDataAccess<About>.Update(new string[] { "Title", "Content" }, new object[] { "title", "content" }, new Condition("ID = @id", 13)); //更新id=13的记录的两个字段
object[] ids = SimpleDataAccess<About>.SelectSingleColumn("ID", null); //获取某一个字段的全部记录
About about = SimpleDataAccess<About>.SelectTopOne(new Condition("ID", "id", OP.Equals, 13)); //获取ID==13的第一条记录
About[] abouts2 = SimpleDataAccess<About>.SelectPage("ID", null, null, 3, 2); //获取默认排序的所有记录的每页3条记录的第3页
About[] abouts3 = SimpleDataAccess<About>.SelectPage("ID", new Condition("ID > @id", 13), new OrderList("ID", true), 3, 1); //获取ID>13,按ID倒序排序的所有记录的每页3条的第1页
SimpleDataAccess<About>.Delete(new Condition("ID = @id", 100)); //删除id = 100的纪录
About[] abouts = SimpleDataAccess<About>.Select(null, null); //获取所有记录,按照默认的排序
SimpleDataAccess<About>.Update(new string[] { "Title", "Content" }, new object[] { "title", "content" }, new Condition("ID = @id", 13)); //更新id=13的记录的两个字段
object[] ids = SimpleDataAccess<About>.SelectSingleColumn("ID", null); //获取某一个字段的全部记录
About about = SimpleDataAccess<About>.SelectTopOne(new Condition("ID", "id", OP.Equals, 13)); //获取ID==13的第一条记录
About[] abouts2 = SimpleDataAccess<About>.SelectPage("ID", null, null, 3, 2); //获取默认排序的所有记录的每页3条记录的第3页
About[] abouts3 = SimpleDataAccess<About>.SelectPage("ID", new Condition("ID > @id", 13), new OrderList("ID", true), 3, 1); //获取ID>13,按ID倒序排序的所有记录的每页3条的第1页
以上演示的是由SimpleDataAccess<EntityType>这个范型类定义的最常用最简单的数据库操作,如果需要更复杂的操作,可以访问另一个类SimpleDbHelper,如果需要比SimpleDnHelper类更灵活复杂的数据库操作,可以直接访问Database类,这里我就不演示了,感兴趣的朋友可以自己尝试。是不是感觉非常简单呢?如果需要事务支持,在.Net2.0下时非常简单的,只需要引用System.Transactions命名空间的类就行,可以参照MSDN文档,我这里就不演示了。
以上的演示中值得注意的是,SimpleDataAccess<EntityType>这个范型类同样代替了原来我为每个实体类生成的一个DataAccess类,因为现在利用范型的缘故,只需要定义一个公共的类,调用时动态邦定About到EntityType参数就相当于以前的一个独立的类AboutAccess了。实在是很大的简化。具体的大家可以自己下载示例代码。
可见,凡是用到范型模版参数的类,都能代替一批类,从而大大简化整体的软件构架,个人觉得这是范型带来的最大的好处,能够极大的较少以前对代码生成的依赖。当然,同时,范型也带来了许多以前不能使用的语法,特别是继承体系的新的组织方式。最典型的语法如:public class ClassA<T> where T : ClassA<T>, new() { ... }这样的类定义。这个本人也在持续研究中,非常有意思,有机会再和大家独立出来探讨。
2 下载
Helper.TestApp.zip
3 关于下载文件的说明
1)以上zip包未包含持久化组件的源码(抱歉~~,以后可能会公开提供源码,不过如果你反编译的话我也不介意^-^);
2)zip包中包含了持久化组件Helper.Data.dll, Helper.Data.dll.SqlServer.dll, Helper.Data.MsAccess.dll的程序集(见Helper.TestApp\bin\Debug目录),可以直接引用;
3)Helper.TestApp\db目录包含了演示用的MsAccess数据库以及SqlServer数据库脚本,演示程序默认操作dbzzz.mdb数据库,如果需要尝试操作SqlServer,可以参照上面的config格式修改为SqlServer方式,并在SqlServer中建一个数据库并运行sql_createtable.sql脚本;
4)zip包中包含了演示程序源码,需要vs2005以上版本才能打开和编译。
01/04 更新
本文续篇:来一点反射,再来一点Emit —— 极度简化Entity!