[转载]SQL CODE

mikel阅读(1266)

[转载]【汇总】SQL CODE — 经典·精彩 – 学无止境 – 博客园.

数据操作类 SQLHelper.cs
http://www.cnblogs.com/zengxiangzhan/archive/2009/12/31/1636871.html

无限级分类 存储过程
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/04/1639220.html

百万级分页存储
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/12/1565313.html

SQL经典短小代码收集
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/12/1565320.html

学生表 课程表 成绩表 教师表 50个常用SQL语句
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/23/1572276.html

SQL SERVER 与ACCESS、EXCEL的数据转换
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/15/1567329.html

游标
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638165.html

根据不同的条件查询不同的表
http://www.cnblogs.com/zengxiangzhan/archive/2009/12/05/1617458.html

INNER JOIN 语法
http://www.cnblogs.com/zengxiangzhan/archive/2009/12/04/1617186.html

master.dbo.spt_values 的妙用
http://www.cnblogs.com/zengxiangzhan/archive/2009/11/24/1609197.html

关于SQL时间类型的模糊查询
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/23/1572286.html

排名 sql
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/23/1572290.html

同步两个数据库
http://www.cnblogs.com/zengxiangzhan/archive/2009/11/15/1603267.html

QLServer和Oracle常用函数对比
http://www.cnblogs.com/zengxiangzhan/archive/2009/09/17/1568816.html

国内最新最全面IP数据库
http://www.cnblogs.com/zengxiangzhan/archive/2010/03/21/1691051.html

sql 先进先出 库存
http://www.cnblogs.com/zengxiangzhan/archive/2009/11/22/1608079.html

BOM 创建用户定义函数,每个子节点de父节点的信息

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/04/1639181.html
用PARSENAME函数拆分字符串

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638183.html

将一个字符串分成多列
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638172.html

字符串分割函数–拆分成多行

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638171.html

合并多行的某一列值
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638169.html

列和相减 L3-L2
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638153.html

统计交叉相等两列元祖的次数 去掉重复
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638150.html

行列 转换 合并 分拆
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638185.html

SQL 年龄段 分组统计
http://www.cnblogs.com/zengxiangzhan/archive/2010/02/04/1663468.html

每个分类取最新的几条的SQL实现

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/05/1639886.html

小计 合计 -统计
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638155.html

求每天的收入和支出
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638149.html

用户消费总金额 2000以下 2000-4000 4000-6000

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638193.html

有小时、分钟,求平均工作时间

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638161.html

电话通话次数以及时长
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638162.html

时间天数相差5天以上并且记录条数大于2的信息

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638194.html

统计文章各种分类总数
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638158.html

近期价格处于降价趋势(至少调了3次)的所有商品
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638156.html

班级考试人数大于10、班级最低分在50分以上、计算机学院、班级平均分从高到低前10名

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638163.html

取出点击量最高的文章100篇,每个作者不超过5篇
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638147.html

如何向一个自增字段插值
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638160.html

查字段指定数据后一行记录

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638192.html

找出与某id相近的四条记录

http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638178.html
停止数据库的用户连接
http://www.cnblogs.com/zengxiangzhan/archive/2010/01/03/1638164.html

解决并清除SQL被注入<script>恶意病毒代码的语句

http://www.cnblogs.com/zengxiangzhan/archive/2010/02/08/1665660.html

作者:曾祥展
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]SQL Server 2008 安全性——透明数据加密(TDE)

mikel阅读(1082)

[转载]SQL Server 2008 安全性——透明数据加密(TDE) – Sai~ – 博客园.

SQL Server 2008 安全性——透明数据加密(TDE)

SQL Server 2005的安全性来说,做的不够好,比如EKM这种可扩展的外部密钥管理就不支持,包括对数据库文件本身的加密。

SQL Server 2008中增加了透明数据加密TDETDE使用DEK 数据库加密密钥对数据文件进行加密,包括IO操作,加入内存等都是存在加密和解密操作的。DEK本身是受master数据库下的证书保护的,当然也支持EKM模块包含。我们对数据文件可以使用标准的DES AES加密。

在某些特定场合,比如我们的服务器发生盗窃行为的时候,一些关键性数据库 不被恶意附加挖掘其中的价值数据,除了使用硬件级别的文件格式加密。SQL Server TDE来保护文件安全。

我主要通过T-SQL 脚本的形式描述 整个加密以及分 离、附加数据库的流程。

/*

Title:TDE加密
Author:浪客
Environment:Windows Server 2008 Enterprise + SQL Server 2008 Enterprise
Description: 请在非生产环境下测试
*/

USE [master];
GO

查看master数据库是否被加密
SELECT name,is_master_key_encrypted_by_server FROM sys.databases;

创建master数据库下的主数据库 密钥
CREATE MASTER KEY ENCRYPTION BY PASSWORD = N浪客!@#$%^&*()0A;

查看master数据库下的密钥信息
SELECT * FROM sys.symmetric_keys;

创建证书用来保护 数据库加密密钥 (DEK)
CREATE CERTIFICATE master_server_cert WITH SUBJECT = NMaster Protect DEK Certificate;

IF DB_ID(db_encryption_test) IS NOT NULL
DROP DATABASE db_encryption_test

创建测试数据库
CREATE DATABASE db_encryption_test;
GO

USE db_encryption_test;

创建由 master_server_cert保护的DEK 数据库加密密钥 (对称密钥)
CREATE DATABASE ENCRYPTION KEY
WITH ALGORITHM = AES_128
ENCRYPTION
BY SERVER CERTIFICATE master_server_cert;
GO

执行上语句以后出现:
/*

Warning: The certificate used for encrypting the database encryption key has not been backed up.

You should immediately back up the certificate and the private key associated with the certificate.
If the certificate ever becomes unavailable or if you must restore or attach the database on another server,
you must have backups of both the certificate and the private key or you will not be able to open the database.
*/
提示你,立刻备份证书;这里备份证书,不比制定加密私钥的 对称密钥了.因为他的密 钥是通过master数据库的主数据库密钥加密了.
USE master;
BACKUP CERTIFICATE master_server_cert TO FILE = D:\MSSQL\Certificate\master_server_cert.cer
WITH PRIVATE KEY (
FILE = D:\MSSQL\Certificate\master_server_cert.pvk ,
ENCRYPTION
BY PASSWORD = 浪客!@#$%^&*()0A );

相应的,我 们也备份一下数据库主密钥(master)
USE master;
如果没有启用主密钥的自动解密功能
OPEN MASTER KEY DECRYPTION BY PASSWORD = ‘浪客!@#$%^&* ()0A’;
BACKUP MASTER KEY TO FILE = D:\MSSQL\MasterKey\master.cer
ENCRYPTION
BY PASSWORD = 浪客!@#$%^&*()0A;
GO

生产环境下,设置成单用户在运行加密
ALTER DATABASE db_encryption_test SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
GO

备份成功以后,开启TDE 加密
ALTER DATABASE db_encryption_test SET ENCRYPTION ON;
GO

设置多用户访问
ALTER DATABASE db_encryption_test SET MULTI_USER WITH ROLLBACK IMMEDIATE;
GO

查看db_encryption_test数据库是否被加 密  encryption_state:3 TDE加密了
SELECT DB_NAME(database_id),encryption_state FROM sys.dm_database_encryption_keys;
/*
发 现tempdb也被加密了。MSDN解释是:如果实例中有一个数据库启用了TDE加密,那么tempdb也被加密
*/

接下来,找另外一台机器或者实例来测试,如果数据文件被盗走了,防止附加的测试.
USE master;
EXEC sp_detach_db Ndb_encryption_test;
GO

将文件QQ发到了另外的机 器,我同事 CL .

USE master;
我先在他机器还原了MASTER KEY (他原机器master库无master key)
RESTORE MASTER KEY
FROM FILE = C:\Users\Administrator\Desktop\master.cer
DECRYPTION
BY PASSWORD = 浪客!@#$%^&*()0A
ENCRYPTION
BY PASSWORD = 浪客!@#$%^&*()0A;
GO

如果没有自动加密
OPEN MASTER KEY DECRYPTION BY PASSWORD=N浪客!@#$%^&*()0A;
创建证书
CREATE CERTIFICATE master_server_cert
FROM FILE = C:\Users\Administrator\Desktop\master_server_cert.cer
WITH PRIVATE KEY (FILE = C:\Users\Administrator\Desktop\master_server_cert.pvk,
DECRYPTION
BY PASSWORD = 浪客!@#$%^&*()0A);
GO
附加数据库
CREATE DATABASE db_encryption_test
ON PRIMARY
(
FILENAME
=NC:\Users\Administrator\Desktop\db_encryption_test.mdf
)
LOG ON
(
FILENAME
=NC:\Users\Administrator\Desktop\db_encryption_test_log.ldf
)
FOR ATTACH ;
GO

测试成功,GG,GL

关闭数据库联接
CLOSE MASTER KEY

通常我们使用某种新特性的时候,还会关注他对SQL Server其他组件的影响,包括复制服务以及数据库镜像以 及日志传送。复制是通过标志事务日志传送标识日志的方式同步数据,当时通过bcp生成的快照文件,不会采用TDE加密数据,这种存在传输信道的 我们通常使用OSIpSecSSL,TSL方式来加密连接,这种在以后的文章会向大家提到。而数据镜像和日志通过传 送日志的方式,所以传输过程中的日志文件是加密的。

/Files/bhtfg538/MSSQL/Security/Encryption/encry_1.txt

[转载]PowerDesigner逆向工程图文讲解

mikel阅读(999)

[转载]PowerDesigner逆向工程图文讲解–温习老知识 – 波哥###从理论到实践,从实践到抽象,从抽象到通俗,从通俗到理论 – 博客园.

我这是被B着写这个图文讲解,我已经三次忘了前面的几个步骤了,所以说忘却真是个可怕的东西,别以为自己掌握了什么什么,其实总会有忘却的一天,多 多温习,养成好习惯。

连续进2家公司,产品都没有一份完整的PDM设计,没办法只能用PD的逆向工程来解决了。

首先,自己先建个PDM,这个我就不说了。

第一步,如图点击DataBase的Reverse Engineer Database[逆向工程数据库], 其实说白了,还是咱的英语差。

点击“确定”

选择系统DSN,点击 “添加”

选择“SQL Server” [当然你是什么数据库,选什么,本例中是SQLServer2005],点击“完成”

名称中填入自己的要取的 名字,并选择数据服务器,点击“下一步”

输入登入账号密码,点击 下一步

选择自己想要逆向工程的 数据库,点击下一步

测试数据源,测试成功的 话,就表示数据源已经连上,点击确定

在Machine data source 中选我们刚刚创建的数据源。

输入数据源的账号密码,

Using an ODBC data source 中会有我们的数据源FDA,点击确定

点击OK,就可以产生 PDM了。

[转载]我是如何解决WordPress无法登陆问题的

mikel阅读(1453)

[转载]白板报 » 我是如何解决Wordpress无法登陆问题的.

作者:wangpei

分类:乱炖

标签:升 级, 无 法登陆, 乱炖, warning, wordpress

评论:12 个评论

前后花费了三个晚上,我终于解决了Wordpress升级后,用户名、密码都正确,却无法登陆的问题。为了让后来者不吃二 遍苦,不受二茬罪,我把解决的详细过程与思路,记录如下。

一、问题描述

这一问题,有几种描述方式,为了让搜索引擎能够找到这篇文章,我把各种常用的叫法汇集如下:

1、有以症状命名的,例如:wordpress无法登陆,密码正确、登录不了,锁到wordpress之外,可以访问、无法登录;

2、有以成因命名的,例如:wordpress升级出错,wordpress2.6升级无法登陆……

3、有以出错语句命名的,这种方式最多:

1) Warning: Invalid argument supplied for foreach() ……
2)capabilities.php on line 31
3)Warning: Cannot modify header information – headers already sent by (output started at……

4、一篇法国人写的著名的解决方案,是这样命名的:Invalid argument supplied for foreach() in wp-capabilities.php: Case Cracked!
(需要指出的是,这篇文章虽然被搜索得很多,但极具有误导性,害了不少人,我认为完全在胡说八道!下文会谈及)

5、有以痛苦感受命名的:天哪,我登陆不上博客了,救命啊,雪地翻跟头跪求……
用这种命名方式搜索,田螺姑娘都没办法帮你。

二、出错语句

出错信息除了上面描述的语句之外,还有其他形式,试搜集如下:

1、我的出错信息:

Warning: Invalid argument supplied for foreach() in /homepages/23/d208744272/htdocs/wp-includes/capabilities.php on line 31

Warning: Cannot modify header information – headers already sent by (output started at /homepages/23/d208744272/htdocs/wp-includes/capabilities.php:31) in /homepages/23/d208744272/htdocs/wp-includes/pluggable.php on line 552

Warning: Cannot modify header information – headers already sent by (output started at /homepages/23/d208744272/htdocs/wp-includes/capabilities.php:31) in /homepages/23/d208744272/htdocs/wp-includes/pluggable.php on line 689

2、以下大同小异,但肯定有这一句:

Warning: Invalid argument supplied for foreach() in /home/user/wp/wp-includes/capabilities.php on line 31

三、出错诱因

目前来看,这个问题大部分情况是发生在为wordpress升级时,不但从2.5升到wordpress2.6,也看到过从2.2或者更低版本升 级,遇到这一情况的。

也有案例显示,安装时就出现这一错误。这个非常罕见。

四、出错原因

我不懂技术,网上有各种说法,有说是uft-8编码转换出了问题,有人说陷入死循环。

这些都不重要,重要的是解决办法,难道不是吗?

五、修改字符编码法,或set names utf8法

这种办法,网上最多。它提供的解决办法是:

第一步:用phpMyadmin后台,把字符编码修改为utf-8。这里要注意,如果你用的是mySQL4.0话,当你把字符修改成utf-8后, 它下次还会显示gb2312,甭理它,其实已经改好了,我的经验是这样。

还有人说,还应该设置数据库的”collation”为”utf8_general_ci”:因为在新的服务器中建立数据库时,没有指定其为 utf-8,更没设定collation,所以需要先设定数据库的字符集。

执行命令:
alter database myblog
character set utf8;

然后再在”操作”或者数据库PhpMyAdmin的登陆页面中设定collation为”utf8_general_ci”。

第二步:修改/wp-includes/wp-db.php这个文件。

这一学派认为,毛病出在wp-db.php中,没有设定好utf-8为字符集,从而造成了一系列悲剧。因此,必须修改这个文件。这一学派又有下属三 个分支流派:

流派一:在$this->dbh = @mySQL_connect($dbhost, $dbuser, $dbpassword);后面加上$this->query(”set names ‘utf8′”); 注意英文标点。

该学派认为,出错原因是wp-includes/capabilities.php里面$this->roles这个数组取到的是乱码。

流派二:就是害人的法国人的那个方案,他认为应该在上述同意位置,加上这一语句:mySQL_query(”SET NAMES ‘utf8′”);,其实这是错误的!!

流派三:还有人主张$this->query(”set names ‘utf8′”);这个语句应该加到wp-db.php的另外一个位置;另外还要增加另外一处代码。我之所以,没有详细列出来,是因为这种办法根本无效。

我试过以上三种办法,最后保留了流派一的办法。

但我强烈怀疑,这种办法的有效性!!

六、胡说八道法,又名检查wp_options表法,又又名wp_user_roles法

这个办法是法国人发明的,他说,在phpMyadmin里,打开wp_options表,找到wp_user_roles一项,从中发 现一个法语怪字符,Abonné,他认为正是这顶小帽子害得程序成为死循环,于是把它改成英文字符,于是立即神奇般地好了。

这个法国人还说了一句阿基米德的名言:Eurêka! ,希腊语,我找到了!真是糟蹋先贤。

实际上这个办法是最害人的。我照着把长达3公里的代码仔细检查,把所有中文都换成英文,结果依然故我。像 这样被误导而浪费时间的不止我一个

七、个别成功法,又名清除cookies法

有人骄傲地宣布,解决了wordpress2.6升级后无法登陆的问题,就是清除cookies。我试了,发现这个办法是无效的。

但这启发了我,有时候你意想不到的办法,反而就是正解。

八、有效办法,又名土耳其补丁法

我在彻底绝望的时候,做了几件事:把wordpress2.6删除,换回了wp2.5。

第二尝试数据库倒裤无果之后,我抱着试试看的办法,找到了这个帖子

这是土耳其人写的一个小补丁,他说只对2.3.X版本有效。但根据我的经验,后续版本应该也可以兼容。

于是我下载了这个插件,http://file.dmry.net/blog/01/wp_user_roles_yama.zip

解压后,将它放到wordpress安装根目录下。就是这副样子:(http://your_blog_url.. /wp_user_roles.php)

然后敲入上述网址:http://your_blog_url../wp_user_roles.php

满屏都是字符串。

等尘埃落定,我不敢相信,我真的做到了,我又可以自由登陆我心爱的WORDPRESS了。

[原创]ASP.NET MVC调用Delphi开发的Web报表打印activex组件

mikel阅读(2584)

最近需要做打印发货单的功能,以前用delphi的reportmachine控件做报表,功能很强大,可以设计报表并且报表文件和程序分离,优点就不多说了,这次需要连纸打印,于是想到用activex组件开发打印组件。

这样这个教程就分为两部分,一部分说Delphi的activex组件开发,另一部说ASP.NET MVC如何调用activex组件进行打印。

1.Delphi开发Activex组件

  • 创建一个ActiveXform项目,如图所示:

    创建activexform

    创建activexform

  • 设置项目发布选项,需要提醒的是由于Delphi7的bug,只在第一次创建项目后能够设置Web Deploy选择,等关闭项目再打开就不能设置了,这个需要注意!
  • 设置activexform项目
  • 设置activexform项目
  • 设置Web delpyment  option
  • 拖拽RMRport控件到窗体中,拖拽RMUserDataset组件到窗体,这个适用于传递数据给RMReport报表打印用的,

    添加RMReport打印组件到窗体添加RMUserDataset

    添加RMReport打印组件到窗体

  • 设置RMReport1的DataSet属性为RMUserDataset1设置Dataset
  • 声明对外方法和属性:选择 View ->Type Library

创建接口方法

声明print方法

声明print方法参数

实现print方法打印

实现print方法打印

注册Activex组件

注册Activex组件

编译生成Activex组件

编译生成Activex组件

到此已经成功创建了一个带打印功能的ActiveX组件,具体如何使用ReportMachine 报表组件的教程,稍后我会写个详细的教程

下面就是.Net如何调用Activex组件了。

2.ASP.NET MVC 调用Activex组件打印

添加Activex组件的引用

1.添加Activex组件的引用

选择Delphi的报表Activex组件

2.选择Delphi的报表Activex组件

引用报表文件到Content文件夹

3.引用报表文件到Content文件夹

4.在About.aspx.cs的onload时间中调用Delphi的Activex组件声明的print方法进行打印,需要赋予服务器上report.rmf也就是报表文件的绝对地址给print方法,这样报表组件才能正确加载报表文件,具体代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ActiveFormProj1;

namespace TangCMS.Views.Home
{
 public partial class About : ViewPage
 {
 protected override void OnLoad(EventArgs e)
 {
 //声明Activex组件的ActivexForm对象
 ActiveFormX report = new ActiveFormX();
 //调用Activex组件声明的print方法
 string file = Server.MapPath("~/content/report.rmf");
 report.Print(file);

 }
 }
}

5.发布站点,点击About Us链接会弹出报表预览打印窗口,最终效果如下:

打印效果最终效果

打印效果最终效果

有困难找八戒

[转载]C#发现之旅第十二讲 基于反射和动态编译的快速ORM框架

mikel阅读(884)

[转载]C#发现之旅第十二讲 基于反射和动态编译的快速ORM框架 – ASP.NET 报表软件 南京袁永福 – 博客园.

系 列课程说明

为了让大家更深入的了解和使用C#,我们将开始这一系列的主题为“C#发现之旅”的技术讲座。考虑到各位大多是进行WEB数据库开发的,而所谓发 现就是发现我们所不熟悉的领域,因此本系列讲座内容将是C#在WEB数据库开发以外的应用。目前规划的主要内容是图形开发和XML开发,并计划编排了多个 课程。在未来的C#发现之旅中,我们按照由浅入深,循序渐进的步骤,一起探索和发现C#的其他未知的领域,更深入的理解和掌握使用C#进行软件开发,拓宽 我们的视野,增强我们的软件开发综合能力。

课程说明

在上次课程中,我们使用.NET框架提供的特性和反射来创建了一个简单的ORM框架,在这个框架中,由于频繁的进行比较慢的反射操作,因此ORM框架运行速度比较慢,在操作大批量的数 据时性能比较差。在本次课程中我们在原先的基础上加上动态编译的技术来实现快速ORM框架。快速ORM框架将不会有性能问题。点 击下载本课程的C#2005的演示代码 http://files.cnblogs.com/xdesigner/MyFastORM.zip

动态编译技术

所谓动态编译技术就是应用程序在运行时,程序内部自动的生成C#代码,然后调用.NET框架提供的C#程序编译器生成临时的程序集,然后将临 时程序集加载到应用程序域中动态的调用其中的对象模块。

动态编译技术内部调用了代码生成器。以前我们是在编程时使用代码生成器生成代码文档,然后添加到C#工程中,然后进行整体编译,此时我们是 手工的使用代码生成器,这个过程可以称为静态编译。而动态编译技术却是将这个过程自动化了,而且调用代码生成器生成代码文本的过程放置在软件运行时执行。

动态编译技术能同时兼顾灵活性和性能。微软.NET框架本身也有动态编译技术的应用,比如XML序列化和反序列化,ASP.NET框架处理ASPX文件等等。

一般而言使用动态编译技术的过程可以为

1. 应用程序需要调用动态编译功能,则收集一些参数,然后调用动态编译模块。

2. 动态编译模块内部有一个全局的临时编译的程序集的缓存列表,若根据应用程序传递的参数可以在缓存列 表中找到相匹配的临时程序集则直接返回这个程序集对象。

3. 动态编译模块收集参数,然后调用内置的代码生成器生成代码字符串。

4. 动态编译模块调用微软.NET框架提供的C#代码编译器,生成一个临时的程序集对象。具体就是调用Microsoft.CSharp.CSharpCodeProvider 提供的方法。在这个过程中,程序将会在磁盘上生成若干临时文件,这个过程会受到微软.NET框架的安全设置的影响。

5. 将临时编译生成的程序集对象保存到全局的临时程序集的缓存列表,然后向应用程序返回这个临时程序 集,而应用程序将会使用反射的手段来调用临时程序集提供的功能。

动态编译技术中生成的临时程序集和我们使用开发工具生成的程序集没有差别,运行速度是一样的快。因此 动态编译技术除了能实现灵活的功能外还提供良好的性能。

我们要使用动态编译技术,首先得看要实现的功能是否灵活多变,若我们要实现的功能比较简单,使用静态 编译技术就足够了,那我们就用不着使用动态编译技术。若功能非常复杂,无法使用代码生成器生成代码来实现它,则也不能使用动态编译技术。

注意,动态编译技术会在磁盘中生成临时文件,因此.NET框架的安全设置会影响到动态编译技术 的正常运行,而且使用该技术的程序会生成C#代码并保存到临时文件,然后调用.NET框架的C#代码编译器生成临时程序集,而恶意软件会在这两个步骤间隙迅速的修改C#代码文件并插入恶意代码,对此动态编译技 术无法判别。

快速ORM框架整体设计

在这里我们将以上节课的ORM框架为基础,对它进行改造,加入动态编译技术来打造一个快速ORM框架。首先我们还得使用BindTableAttributeBindFieldAttribute特性 来描述实体类型和数据库的绑定信息。于是我们上节课使用的演示用的实体类型DB_Employees就原封不动的用到现在。该实体类型的代码为

[System.Serializable()]

[BindTable(“Employees”)]

public class DB_Employees

{

/// <summary>

/// 人员全名

/// </summary>

public string FullName

{

get

{

return this.LastName + this.FirstName ;

}

}

#region 定义数据库字段变量及属性 //////////////////////////////////////////

///<summary>

/// 字段值 EmployeeID

///</summary>

private System.Int32 m_EmployeeID = 0 ;

///<summary>

/// 字段值 EmployeeID

///</summary>

[BindField(“EmployeeID” , Key = true )]

public System.Int32 EmployeeID

{

get

{

return m_EmployeeID ;

}

set

{

m_EmployeeID = value;

}

}

///<summary>

/// 字段值 LastName

///</summary>

private System.String m_LastName = null ;

///<summary>

/// 字段值 LastName

///</summary>

[BindField(“LastName”)]

public System.String LastName

{

get

{

return m_LastName ;

}

set

{

m_LastName = value;

}

}

其他字段……………..

#endregion

}// 数据库操作类 DB_Employees 定义结束

我们设计快速ORM框架的程序结构如图所示

框架中包含了一个实体类型注册列表,列表中包含了实体类型和相应的RecordORMHelper对象。应用程 序在使用框架前必须注册实体类型,向实体类型注册列表添加将要操作的实体类型,应用程序注册实体列表时不会立即导致代码的自动生成和编译。

我们首先定义了一个基础的抽象类型RecordORMHelper,该类型定义了处理实体类型和数据库的映射操作,主要包括从一个System.Data.IDataReader读取数据并创建实体类型,为新增,修改和删除数据库记录而初始化System.Data.IDbCommand对象等等。该类型是快速ORM框架的核心处理对象,数据库处理模块将使用RecordORMHelper来作为统一的接口来处理实体类和数据库的映射操作。

代码生成器分析实体类型列表中所有没有处理的实体类型,获得其中的使用BindTableAttributeBindFieldAttribute特性 保存的对象和数据库的映射关系,针对每一个实体类型创建一个Class的代码,该Class是从RecordORMHelper上派生的,并实现了RecordORMHelper预留的接口。代码生成器可以同时为多个实体类型创建C#源代码,此时一份C#源代码中包含了多个从RecordORMHelper派生的Class类。

C#代码编译器接受代码生成器生成的代码,进行编译生成一个临时程序集,该程序集中就包含了多个派生自RecordORMHelper的类型,每 一个类型都专门处理某种实体类型。编译器在编译程序是需要指定所引用的其他程序集,这里包括 mscorlib.dllSystem.dllSystem.Data.dll,此外还包 括类型RecordORMHelper所在的程序集,也就是包括快速ORM框架的程序集,这里的程序集不一定是DLL格式,也可能是EXE格式。于是我们编译程序时引用了一个EXE,这种操作在使用VS.NET等开发工具时是禁止的。从这里可以看出,一些使用VS.NET开发工具所不可能实现的功能我们 可以编程使用.NET框架来实现。

.NET框架自己包含了一个C#代码编译器,它的文件名是CSC.EXE,在.NET框架的安装目录下,在笔者的电脑中其路径是 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\csc.exe 或者 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe ,它是一个基于命令行的编辑器,能将C#代码编译生成EXE或者DLL文件。关于C#代码编译器可参考MSDN中的相关说明。

快速ORM框架的控制模块接受应用程序的请求,首先检查实体类型注册列表,若列表中没有找到相应的RecordORMHelper对象,则调 用代码生成器生成代码,然后调用C#代码编译器编译生成临时的程序集,然后加载临时程序集,使用反射(调用System.Reflection.Assembly.GetType函数)找到其中所有的的RecordORMHelper类型,然后根据类型动态的创建对象实例,并填充到实体类型注册列表。最后调用RecordORMHelper预定的接口 来实现ORM功 能。

若我们在使用快速ORM框架前,将所有可能要用到的实体对象类型添加到实体类型注册列表中,则快速ORM框架会生成一个临时程序集,但我们是 陆陆续续的往ORM框 架注册实体对象类型,则快速ORM框架内部可能会多次调用代码生成器和代码编译器来生成临时程序集,这样最后会生成多个临时程序集。一 般的建议在使用框架前将向ORM框架注册所有可能用到的实体对象类型,这样框架只会执行一次动态编译的操作。

基础类型RecordORMHelper

本类型属于ORM框架的底层模块。其代码为

public abstract class RecordORMHelper

{

/// <summary>

/// 对象操作的数据表名称

/// </summary>

public abstract string TableName

{

get ;

}

/// <summary>

/// 从数据读取器读取数据创建一个记录对象

/// </summary>

/// <param name=”reader”>数据读取器</param>

/// <returns>读取的数据</returns>

public object ReadRecord( System.Data.IDataReader reader )

{

int[] indexs = GetFieldIndexs( reader );

return InnerReadRecord( reader  ,indexs );

}

/// <summary>

/// 从数据读取器读取数据创建若干个记录对象

/// </summary>

/// <param name=”reader”>数据读取器</param>

/// <param name=”MaxRecordCount”>允许读取的最大的记录个数,为0则无限制</param>

/// <returns>读取的数据对 象列表</returns>

public System.Collections.ArrayList ReadRecords( System.Data.IDataReader reader , int MaxRecordCount )

{

System.Collections.ArrayList list = new System.Collections.ArrayList();

int[] indexs = GetFieldIndexs( reader );

while( reader.Read())

{

object record = InnerReadRecord( reader , indexs );

list.Add( record );

if( MaxRecordCount > 0 && list.Count >= MaxRecordCount )

{

break;

}

}//while

return list ;

}

/// <summary>

/// 从一个数据读取器中读取一条记录对象,必须重载

/// </summary>

/// <param name=”reader”>数据读取器</param>

/// <param name=”FieldIndexs”>字段序号列表</param>

/// <returns>读取的记录对 象</returns>

protected abstract object InnerReadRecord( System.Data.IDataReader reader , int[] FieldIndexs );

/// <summary>

/// 为删除记录而初始化数据库命令对象

/// </summary>

/// <param name=”cmd”>数据库命令对象</param>

/// <param name=”objRecord”>实体对象实例</param>

/// <returns>添加的SQL参数个数</returns>

public abstract int FillDeleteCommand( System.Data.IDbCommand cmd , object objRecord );

/// <summary>

/// 为插入记录而初始化数据库命令对象

/// </summary>

/// <param name=”cmd”>数据库命令对象</param>

/// <param name=”objRecord”>实体对象实例</param>

/// <returns>添加的SQL参数个数</returns>

public abstract int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord );

/// <summary>

/// 为更新数据库记录而初始化数据库命令对象

/// </summary>

/// <param name=”cmd”>数据库命令对象</param>

/// <param name=”objRecord”>实体对象实例</param>

/// <returns>添加的SQL参数个数</returns>

public abstract int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord );

/// <summary>

/// 字段列表数组

/// </summary>

protected abstract string[] RecordFieldNames

{

get ;

}

/// <summary>

/// 针对特定的数据读取器获得实体对象的各个属性 对应的数据栏目的编号

/// </summary>

/// <param name=”reader”>数据读取器</param>

/// <returns>编号列表</returns>

private int[] GetFieldIndexs( System.Data.IDataReader reader )

{

if( reader == null )

{

throw new ArgumentNullException(“reader”);

}

string[] FieldNames = this.RecordFieldNames ;

int[] indexs = new int[ FieldNames.Length ] ;

int FieldCount = reader.FieldCount ;

string[] names = new string[ FieldCount ] ;

for( int iCount = 0 ; iCount < FieldCount ; iCount ++ )

{

names[ iCount ] = reader.GetName( iCount ) ;

}

for( int iCount = 0 ; iCount < indexs.Length ; iCount ++ )

{

indexs[ iCount ] = -1 ;

string name = FieldNames[ iCount ] ;

for( int iCount2 = 0 ; iCount2 < FieldCount ; iCount2 ++ )

{

if( EqualsFieldName( name , names[ iCount2 ] ))

{

indexs[ iCount ] = iCount2 ;

break;

}

}

}

for( int iCount = 0 ; iCount < FieldCount ; iCount ++ )

{

string name = reader.GetName( iCount );

for( int iCount2 = 0 ; iCount2 < indexs.Length ; iCount2 ++ )

{

if( EqualsFieldName( name , FieldNames[ iCount2 ] ))

{

indexs[ iCount2 ] = iCount ;

}

}

}

return indexs ;

}

/// <summary>

/// 连接多个字符串,各个字符串之间用逗号分隔,本函数会在动态生成的派生类中使用

/// </summary>

/// <param name=”strs”>字符串集合</param>

/// <returns>连接所得的大 字符串</returns>

protected string ConcatStrings( System.Collections.IEnumerable strs )

{

System.Text.StringBuilder myStr = new System.Text.StringBuilder();

foreach( string str in strs )

{

if( myStr.Length > 0 )

{

myStr.Append(“,”);

}

myStr.Append( str );

}//foreach

return myStr.ToString();

}

/// <summary>

/// 判断两个字段名是否等价

/// </summary>

/// <param name=”name1″>字段名1</param>

/// <param name=”name2″>字段名2</param>

/// <returns>true:两个字段名等价 false:字段名不相同</returns>

private bool EqualsFieldName( string name1 , string name2 )

{

if( name1 == null || name2 == null )

{

throw new ArgumentNullException(“name1 or name2”);

}

name1 = name1.Trim();

name2 = name2.Trim();

// 进行不区分大小写的比较

if( string.Compare( name1 , name2 , true ) == 0 )

{

return true ;

}

int index = name1.IndexOf(“.”);

if( index > 0 )

{

name1 = name1.Substring( index + 1 ).Trim();

}

index = name2.IndexOf(“.”);

if( index > 0 )

{

name2 = name2.Substring( index + 1 ).Trim();

}

return string.Compare( name1 , name2 , true ) == 0 ;

}

#region 从数据库读取的原始数据转换为指定数据类型的函数群,本函数会在动 态生成的派生类中使用

protected byte ConvertToByte( object v , byte DefaultValue )

{

if( v == null || DBNull.Value.Equals( v ))

return DefaultValue ;

else

return Convert.ToByte( v );

}

protected sbyte ConvertToSByte( object v , sbyte DefaultValue )

{

if( v == null || DBNull.Value.Equals( v ))

return DefaultValue ;

else

return Convert.ToSByte( v );

}

其他的 ConvertTo 函数

#endregion

/// <summary>

/// 将日期数据转换为数据库中的格式,本函数会在动态生成的派生类中使用.

/// </summary>

/// <param name=”Value”>日期数据</param>

/// <param name=”Format”>保存格式化字符串</param>

/// <returns>转换后的数据</returns>

protected object DateTimeToDBValue( DateTime Value , string Format )

{

if( Format != null || Format.Length > 0 )

{

return Value.ToString( Format );

}

else

{

return Value ;

}

}

}//public abstract class RecordORMHelper

在这个类型中,TableName属性返回该实体对象类型绑定的数据库名称,因此该属性值由BindTableAttribute特性指 定,RecordFieldNames属性返回一个字符串数组,该数组列出了所有的绑定的字段的名称,也就是实体类型包含的所有的BindFieldAttribute指定 的字段名称组成的数组。

实体类型注册列表

在快速ORM框架主模块MyFastORMFramework中定义了一个myRecordHelpers的变量

private static System.Collections.Hashtable myRecordHelpers = new System.Collections.Hashtable();

这个myRecordHelpers就是实体类型 注册列表。该列表中键值就是实体对象类型,而它的数据值就是一个个动态生成的从RecordORMHelper派生的对象实例。我们定义了一个函数向该列表注册实体对象类型

public void RegisterType( Type t )

{

if( myRecordHelpers.ContainsKey( t ) == false )

{

this.GetBindProperties( t );

myRecordHelpers[ t ] = null ;

}

}

这个过程很简单,就是向该列表的键值列表添加实体对象类型,这里调用了GetBindProperties函数,该 函数内部会仔细检查实体对象类型是否符合快速ORM框架的要求,若不符合则会报错,因此这里调用GetBindProperties函数就是检查实体对象类型是否合格。

ORM框架操作数据库前都会查询实体类型注册列表获得所需的数据库操作帮助器,也就是调用函数GetHelepr,其代码为

private RecordORMHelper GetHelper( Type RecordType )

{

RecordORMHelper helper = null ;

if( myRecordHelpers.ContainsKey( RecordType ))

{

helper = ( RecordORMHelper ) myRecordHelpers[ RecordType ] ;

if( helper != null )

{

return helper ;

}

}

else

{

this.GetBindProperties( RecordType );

myRecordHelpers[ RecordType ] = null;

}

BuildHelpers( null );

helper = ( RecordORMHelper ) myRecordHelpers[ RecordType ] ;

if( helper == null )

{

throw new ArgumentException(为类型 + RecordType.FullName + 初始化系统错误);

}

return helper ;

}

在这个函数中,参数就是实体对象类型,首先从注册列表中获得数据库操作帮助器,若没有找到则进行注 册,然后调用BuildHelpers执行动态编译生成数据库操作帮助器。然后再尝试从注册列表中获得数据库操作帮助器。

ORM框架中,GetHelper函数会频繁的调用,因此使用实体对象类型注册列表可以提高系统性能。应用系统多次连续的调用RegisterType函数会导致类型注 册列表中有多个类型对应的数据库操作帮助器是空的,而再BuildHelpers函数内部会对所有的没有设定数据库操作帮助器的实体对象类型执行动态编译的操作,能一下子生成多个 数据库操作帮助器,这样能尽量减少动态编译的次数。

代码生成器

在动态编译框架中,代码生成器是非常重要的部分。没有代码生成器,动态编译框架成了无源之水,无米之 炊了。代码生成器的主要工作就是使用反射解析数据库实体类的结构,分析其中的数据库绑定信息,然后使用字符串拼凑的操作来生成C#代码字符串。

要设计出代码生成器,首先的设计出其要输出的C#代码的结构,我们可以不使用那个基础的RecordORMHelper而完全依赖生成的C#代码来完成数据库的映射功能,不过即使用代码生成器,我们也得考虑到代码的重用,于是我们把一些通 用的代码放到RecordORMHelper中,然后动态生成的C#类就继承自RecordORMHelper

ORM框架中还包含了一个IndentTextWriter的支持缩进的文本书写器,虽然我们可以完全使用字符串加号操作来生成代码文本,但使用IndentTextWriter能让工作 更高效,生成的代码也便于人们阅读,这有利于代码生成器的调试和维护。在IndentTextWriter中,使用BeginGroup来开始缩进一段代码块,使用EndGroup来结束缩进一段代码块,使用WriteLine来输出一行代码文本。

在快速ORM框架中,代码生成器包含在函数MyFastORMFramework.GenerateCode中。现对其过程进行说明

启用命名参数

MyFastORMFramework中定义了NamedParameter属性用于决定是否启动命名参数。为了安全,代码生成器生成的SQL命令文本不会包含具体的数值,而是使用SQL命令参数的方式。若设置该属性,则启 用命名参数,此时代码生成器生成SQL文本中使用“@参数名”来表示SQL命令参数占位,若没有设置该属性,则未启用命名参数,此时代码生成器生成的SQL文本中使用“?”来表示SQL命令参数占位。比如对于新增记录,若启用命令参数,则生成的SQL文本为“Insert Into Table ( Field1 , Field2 ) Values ( @Value1 , @Value2 )”,若不启用命名参数则生成的SQL文本为“Insert Into Table( Field1 , Field2 ) Values( ? , ? )”。

某些类型的数据库不支持无命名的参数,有些支持,因此本快速ORM框架提供了NamedParamter属性方法让使用者 进行调整,使得快速ORM框架能适用于更多类型的数据库。

生成读取数据的代码

基础类型RecordORMHelper中函数 ReadRecord调用GetFieldIndexsInnerReadRecord函数从一个IDataReader中读取一行数据并创建一个实体类型的实例。GetFieldIndexs 函数用于获得一个整数数组,该数组的元素就是实体类各个属性对应的数据 读取器的从0开始 计算的字段栏目序号。例如对于属性 DB_Employees. EmployeeID,它是对象的第一个属性成员,其绑定的字段是“EmployeeID”。 若数据读取器的第三个栏目,也就是对它调用IDataReader.GetName( 3 )的值 是“employeeid,GetFieldIndexs函数返回的数组第一个元素值就是3。 若数据读取器没有找到和“EmployeeID”相匹配的栏目,则GetFieldIndexs函数返回的数组的第一个元素值是-1。 使用GetFieldIndexs的返回值,ORM框 架可以使用比较快速的IDataReader.GetValue( index )来读取数据而不 必使用慢速的 IDataReader.GetValue( name )了。

InnerReadRecord需要代码生成器来生成代码进行扩展,对于DB_Employees, 其扩展的代码为

protected override object InnerReadRecord( System.Data.IDataReader reader , int[] FieldIndexs )

{

MyORM.DB_Employees record = new MyORM.DB_Employees();

int index = 0 ;

index = FieldIndexs[ 0 ]; // 读取字段 EmployeeID

if( index >= 0 )

{

record.EmployeeID = ConvertToInt32( reader.GetValue( index ) , ( int ) 0) ;

}

index = FieldIndexs[ 1 ]; // 读取字段 LastName

if( index >= 0 )

{

record.LastName = ConvertToString( reader.GetValue( index ) , null ) ;

}

读取其他字段值……

return record ;

}

在这段自动生成的代码中,参数reader就是类型为IDataReader的数据读取器,而FieldIndexs就是GetFieldIndexs的返回值。在InnerReadRecord函数中会一次读取FieldIndexs的元素值,根据属性的数据类型而调用ConvertToInt32ConvertToString等一系列的ConvertTo函数,而这一系列的函数已经在基础类型RecordORMHelper中定义好了。

从这个自动生成的代码可以看出,ORM框架使用实体类的属性,GetFieldIndexs和数据读取器实现了如下的映射过程

在这个过程中,GetFieldIndexs函数提供了一个映射表,而自动生成的代码就是利用这个映射表将数据从DataReader复制到DB_Employees的属性中。

我们自动生成代码实现了InnerReadRecord函数后,在ORM框架中就可以调用基础的RecordORMHelper中的ReadRecord函数读取一行数据并生成一个实体对象,而函数ReadRecordsReadRecord的另外一个读取多个数据的版本。

根据上述设计,我们可以使用以下代码来生成InnerReadRecord代码

myWriter.WriteLine(“// 从数 据读取器读取数据创建对象);

myWriter.WriteLine(“protected override object InnerReadRecord( System.Data.IDataReader reader , int[] FieldIndexs )”);

myWriter.BeginGroup(“{“);

myWriter.WriteLine( RecordType.FullName + ” record = new “ + RecordType.FullName + “();”);

myWriter.WriteLine(“int index = 0 ;”);

// 获得类型中绑定到数据库的属性信息

for( int iCount = 0 ; iCount < ps.Length ; iCount ++ )

{

System.Reflection.PropertyInfo p = ps[ iCount ] ;

if( p.CanWrite == false )

{

continue ;

}

BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(

p , typeof( BindFieldAttribute ));

myWriter.WriteLine(“”);

myWriter.WriteLine(“index = FieldIndexs[ “ + iCount + ” ]; // 读取字段 + GetBindFieldName( p ));

myWriter.WriteLine(“if( index >= 0 )”);

myWriter.BeginGroup(“{“);

Type pt = p.PropertyType ;

object DefaultValue = this.GetDefaultValue( p );

string strRead = null;

if( pt.Equals( typeof( byte )))

{

strRead = “ConvertToByte( reader.GetValue( index ) , “ + GetValueString( pt , DefaultValue ) + “)”;

}

else if( pt.Equals( typeof( sbyte )))

{

strRead = “ConvertToSByte( reader.GetValue( index ) , “ + GetValueString( pt , DefaultValue ) + “)”;

}

else if( pt.Equals( typeof( short )))

{

strRead = “ConvertToInt16( reader.GetValue( index ) , “ + GetValueString( pt , DefaultValue ) + “)”;

}

处理其他数据类型……

else if( pt.Equals( typeof( DateTime )))

{

string strDefault = “DateTime.MinValue” ;

DateTime dt = Convert.ToDateTime( DefaultValue );

if( dt.Equals( DateTime.MinValue ) == false )

{

strDefault = “new DateTime( “ + dt.Ticks + “)”;

}

strRead = “ConvertToDateTime( reader.GetValue( index ) , “ + strDefault + ” , “ + ( fa.ReadFormat == null ? “null” : “\”” + fa.ReadFormat + “\”” ) + ” )”;

}

else if( pt.Equals( typeof( string )))

{

strRead = “ConvertToString( reader.GetValue( index ) , “ + ( DefaultValue == null ? “null” : “@\”” + DefaultValue.ToString() + “\”” ) + ” )”;

}

else if( pt.Equals( typeof( char )))

{

strRead = “ConvertToChar( reader.GetValue( index ) , “ + GetValueString( pt , DefaultValue ) + ” )”;

}

else

{

throw new InvalidOperationException(不支持属性类型 + p.Name + ” “ + pt.FullName );

}

myWriter.WriteLine(“record.” + p.Name + ” = “ + strRead + ” ;” );

myWriter.EndGroup(“}”);

}//for

myWriter.WriteLine(“”);

myWriter.WriteLine(“return record ;”);

myWriter.EndGroup(“)//InnerReadRecord”);

在这段代码中,ps是一个事先分析了DB_Employees结构而得出的System.Rection.PropertyInfo数组,包含了所有附加了BindFieldAttribute的成员属性,它是调用GetBindProperties函数获得的返回值。GetDefaultValue用于获得针对某个属性的默认值,若调用reader.GetValue( index )获得了一个空值,也就是DBNull.Value则设置属性为默认 值;GetValueString是将一个数值转换为C#代码的表达样式。然后针对不同的属性数据类型生成对应的ConvertTo代码。

函数GetBindProperties的代码为

private System.Reflection.PropertyInfo[] GetBindProperties( Type RecordType )

{

if( RecordType == null )

{

throw new ArgumentNullException(“ReocrdType”);

}

if( RecordType.IsPublic == false )

{

throw new ArgumentException(类型 + RecordType.FullName + 不是公开类型);

}

if( RecordType.IsClass == false )

{

throw new ArgumentException(类型 + RecordType.FullName + 不是类);

}

if( RecordType.IsAbstract )

{

throw new ArgumentException(类型 + RecordType.FullName + 不得是抽象类);

}

// 检查是否有可用的无参数的构造函数

// 也就是判断语句 Record obj = new Record() 是否有效

if( RecordType.GetConstructor( new Type[]{}) == null )

{

throw new ArgumentException(类型 + RecordType.FullName + 没有默认构造函数 );

}

System.Collections.ArrayList properties = new System.Collections.ArrayList();

System.Reflection.PropertyInfo[] ps = RecordType.GetProperties(

System.Reflection.BindingFlags.Instance

| System.Reflection.BindingFlags.Public );

foreach( System.Reflection.PropertyInfo p in ps )

{

BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(

p , typeof( BindFieldAttribute ));

if( fa != null )

{

System.Reflection.ParameterInfo[] pps = p.GetIndexParameters();

if( pps != null && pps.Length > 0 )

{

throw new ArgumentException(属性 + RecordType.FullName + “.” + p.Name + 不得有参数);

}

Type pt = p.PropertyType ;

if( pt.IsPrimitive || pt.Equals( typeof( string )) || pt.Equals( typeof( DateTime )))

{

}

else

{

throw new ArgumentException(不支持属性 + RecordType.FullName + “.” + p.Name + 的数据类型 + pt.FullName );

}

properties.Add( p );

}

}

if( properties.Count == 0 )

{

throw new ArgumentException(类型 + RecordType.FullName + 没有标记为绑定到任何字段);

}

return ( System.Reflection.PropertyInfo[] ) properties.ToArray( typeof( System.Reflection.PropertyInfo ));

}

从这个函数可以看出,快速ORM框架处理的实体类型必须是一个类型,必须公开,不得是抽象的,而且有公开的无参数的构造函数。这里 使用了.NET框 架的反射技术,首先使用Type.GetConstructor函数获得对象类型指定样式的构造函数对象,还使用GetProperties函数获得实体类型 的所有的公开实例属性。若属性附加了BindFieldAttribute特性则添加到输出列表中。注意,属性的数据类型必须是CLR基础数据类型,字符串或者时间日期格 式,其他的数据类型是不合要求的。

这里还调用了一个GetBindFieldName获得属性绑定的数据库字段名,其代码为

private string GetBindFieldName( System.Reflection.PropertyInfo p )

{

BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(

p , typeof( BindFieldAttribute ));

string name = fa.Name ;

if( name != null )

name = name.Trim();

if( name == null || name.Length == 0 )

name = p.Name ;

return name ;

}

其功能很简单,就是检查属性是否附加了BindFieldAttribute特性,若附加了则使用该特性的Name值,若没有则直接返回属性的名称。

生成插入数据的代码

基础类型RecordORMHelper预留了FillInsertCommand函数,该函数就是为插入数据库记录而设置数据库命令对象(IDbCommand)的。对于DB_Employees,其代码为

public override int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )

{

if( cmd == null ) throw new ArgumentNullException(“cmd”);

if( objRecord == null ) throw new ArgumentNullException(“objRecord”);

MyORM.DB_Employees myRecord = objRecord as MyORM.DB_Employees ;

if( myRecord == null ) throw new ArgumentException(“must type ‘MyORM.DB_Employees’ “);

System.Collections.ArrayList myFieldNames = new System.Collections.ArrayList();

System.Collections.ArrayList myValues = new System.Collections.ArrayList();

myFieldNames.Add( “EmployeeID” );

myValues.Add( myRecord.EmployeeID );

if( myRecord.LastName != null )

{

myFieldNames.Add( “LastName” );

myValues.Add( myRecord.LastName );

}

myFieldNames.Add( “BirthDate” );

myValues.Add( myRecord.BirthDate.ToString(“yyyy-MM-dd”) );

处理其他属性值……

myFieldNames.Add( “Sex” );

myValues.Add( myRecord.Sex );

if( myFieldNames.Count == 0 ) return 0 ;

cmd.Parameters.Clear() ;

System.Text.StringBuilder mySQL = new System.Text.StringBuilder();

mySQL.Append( “Insert Into Employees ( “ );

mySQL.Append( ConcatStrings( myFieldNames ));

mySQL.Append( ” ) Values ( “ );

for( int iCount = 0 ; iCount < myValues.Count ; iCount ++ )

{

if( iCount > 0 ) mySQL.Append(” , “ );

mySQL.Append(” ? “) ;

System.Data.IDbDataParameter parameter = cmd.CreateParameter();

parameter.Value = myValues[ iCount ] ;

cmd.Parameters.Add( parameter );

}//for

mySQL.Append( ” ) “ );

cmd.CommandText = mySQL.ToString();

return myValues.Count ;

}

在这段代码中,首先是检查参数是否正确。然后处理实体类型的所有的属性。若属性值等于默认值则跳过处 理,否则将属性绑定的字段的名称保存到myFieldNames列表中,属性值保存到myValues列表中。最后使用字符串拼凑的操作来生成SQL命令文本,若NamedParameter属性为Ture,则生成的SQL文本为“Insert Into TableName  ( FieldName1 , FieldName2 … ) Values ( @Value1 , @Value2 …)”,若该属性为False,则生成的SQL文本为“Insert Into TableName  ( FieldName1 , FieldName2 … ) Values ( , …)”,并将属 性值添加到数据库命令对象的参数列表中,该函数返回保存数据的属性的个数。

对于字符串类型的属性,其默认值就是“DBNull”。而对于其他的 整数或者日期类型的属性,并没有默认值,因此是无条件的插入到数据库中。

我们使用以下的代码来生成上述代码文本

myWriter.WriteLine(“public override int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )”);

myWriter.BeginGroup(“{“);

myWriter.WriteLine(“if( cmd == null ) throw new ArgumentNullException(\”cmd\”);”);

myWriter.WriteLine(“if( objRecord == null ) throw new ArgumentNullException(\”objRecord\”);”);

myWriter.WriteLine(RecordType.FullName + ” myRecord = objRecord as “ + RecordType.FullName + ” ;”);

myWriter.WriteLine(“if( myRecord == null ) throw new ArgumentException(\”must type ‘” + RecordType.FullName + “‘ \”);”);

myWriter.WriteLine(“System.Collections.ArrayList myFieldNames = new System.Collections.ArrayList();”);

myWriter.WriteLine(“System.Collections.ArrayList myValues = new System.Collections.ArrayList();”);

for (int iCount = 0; iCount < ps.Length; iCount++)

{

System.Reflection.PropertyInfo p = ps[iCount];

if (p.CanRead == false)

{

continue;

}

BindFieldAttribute fa = (BindFieldAttribute)Attribute.GetCustomAttribute(

p, typeof(BindFieldAttribute));

string FieldName = GetBindFieldName(p);

myWriter.WriteLine(“”);

Type pt = p.PropertyType;

object DefaultValue = this.GetDefaultValue(p);

if (pt.Equals(typeof(string)))

{

myWriter.WriteLine(“if( myRecord.” + p.Name + ” != null && myRecord.” + p.Name + “.Length != 0 )”);

myWriter.BeginGroup(“{“);

myWriter.WriteLine(“myFieldNames.Add( \”” + FieldName + “\” );”);

myWriter.WriteLine(“myValues.Add( myRecord.” + p.Name + ” );”);

myWriter.EndGroup(“}”);

}

else if (pt.Equals(typeof(DateTime)))

{

myWriter.WriteLine(“myFieldNames.Add( \”” + FieldName + “\” );”);

if (fa.WriteFormat != null && fa.WriteFormat.Length > 0)

{

myWriter.WriteLine(“myValues.Add( myRecord.” + p.Name + “.ToString(\”” + fa.WriteFormat + “\”) );”);

}

else

{

myWriter.WriteLine(“myValues.Add( myRecord.” + p.Name + “.ToString(\”yyyy-MM-dd HH:mm:ss\”) );”);

}

}

else

{

myWriter.WriteLine(“myFieldNames.Add( \”” + FieldName + “\” );”);

myWriter.WriteLine(“myValues.Add( myRecord.” + p.Name + ” );”);

}

}//for

myWriter.WriteLine(“”);

myWriter.WriteLine(“if( myFieldNames.Count == 0 ) return 0 ;”);

myWriter.WriteLine(“cmd.Parameters.Clear() ;”);

myWriter.WriteLine(“System.Text.StringBuilder mySQL = new System.Text.StringBuilder();”);

myWriter.WriteLine(“mySQL.Append( \”Insert Into “ + TableName + ” ( \” );”);

myWriter.WriteLine(“mySQL.Append( ConcatStrings( myFieldNames ));”);

myWriter.WriteLine(“mySQL.Append( \” ) Values ( \” );”);

myWriter.WriteLine(“for( int iCount = 0 ; iCount < myValues.Count ; iCount ++ )”);

myWriter.BeginGroup(“{“);

myWriter.WriteLine(“if( iCount > 0 ) mySQL.Append(\” , \” );”);

if (bolNamedParameter)

{

myWriter.WriteLine(“mySQL.Append(\” @Value\” + iCount ) ;”);

myWriter.WriteLine(“System.Data.IDbDataParameter parameter = cmd.CreateParameter();”);

myWriter.WriteLine(“parameter.Value = myValues[ iCount ] ;”);

myWriter.WriteLine(“parameter.ParameterName = \”Value\” + iCount ;”);

myWriter.WriteLine(“cmd.Parameters.Add( parameter );”);

}

else

{

myWriter.WriteLine(“mySQL.Append(\” ? \”) ;”);

myWriter.WriteLine(“System.Data.IDbDataParameter parameter = cmd.CreateParameter();”);

myWriter.WriteLine(“parameter.Value = myValues[ iCount ] ;”);

myWriter.WriteLine(“cmd.Parameters.Add( parameter );”);

}

myWriter.EndGroup(“}//for”);

myWriter.WriteLine(“mySQL.Append( \” ) \” );”);

myWriter.WriteLine(“cmd.CommandText = mySQL.ToString();”);

myWriter.WriteLine(“return myValues.Count ;”);

myWriter.EndGroup(“)//public override int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )”);

在这里我们首先输出检查参数的代码文本,然后遍历所有绑定字段的属性对象,根据属性的数据类型分为字 符串样式,日期样式和其他样式。对于字符串样式则需要输出判断是否为空的代码,对于日期样式则还要考虑BindFieldAttribute特性中 指明的数据保存样式,对于其他样式则没有任何判断,直接输出。

生成删除数据的代码

基础类型RecordORMHelper预留了FillDeleteCommand函数,代码生成器自动生成代码来实现FillDeleteCommand函数,而ORM框架就会创建一个数据库命令对象,然后调用FillDeleteCommand函数来为删除数据而初始化数据库命令对象,然后执行SQL命令删除数据。

DB_Employees中使用一下代码来定义EmployeeID属性的。

///<summary>

/// 字段值 EmployeeID

///</summary>

private System.Int32 m_EmployeeID = 0 ;

///<summary>

/// 字段值 EmployeeID

///</summary>

[BindField(“EmployeeID” , Key = true )]

public System.Int32 EmployeeID

{

get

{

return m_EmployeeID ;

}

set

{

m_EmployeeID = value;

}

}

附加的BindField特性中使用了“Key=true”指明了EmployeeID字段是关键字段。于是 我们很容易就想到使用SQL语句“Delete From Employees Where EmployeeID=指定的员工编号”来删除数据。于是针对DB_Employees代码生成器生成的 代码如下

public override int FillDeleteCommand( System.Data.IDbCommand cmd , object objRecord )

{

if( cmd == null ) throw new ArgumentNullException(“cmd”);

if( objRecord == null ) throw new ArgumentNullException(“objRecord”);

MyORM.DB_Employees myRecord = objRecord as MyORM.DB_Employees ;

if( myRecord == null ) throw new ArgumentException(“must type ‘MyORM.DB_Employees’ “);

cmd.Parameters.Clear();

cmd.CommandText = @”Delete From Employees Where EmployeeID = ? “ ;

System.Data.IDbDataParameter parameter = null ;

parameter = cmd.CreateParameter();

parameter.Value = myRecord.EmployeeID ;

cmd.Parameters.Add( parameter );

return 1 ;

}

我们可以使用以下代码来生成上述的C#代码文本。

// 关键 字段SQL参数名称列表

System.Collections.ArrayList KeyParameterNames = new System.Collections.ArrayList();

// 生成Where子语句文本

System.Text.StringBuilder myWhereSQL = new System.Text.StringBuilder();

System.Collections.ArrayList KeyProperties = new System.Collections.ArrayList();

for (int iCount = 0; iCount < ps.Length; iCount++)

{

System.Reflection.PropertyInfo p = ps[iCount];

if (p.CanRead == false)

{

continue;

}

BindFieldAttribute fa = (BindFieldAttribute)Attribute.GetCustomAttribute(

p, typeof(BindFieldAttribute));

if (fa.Key == false)

{

continue;

}

string FieldName = this.GetBindFieldName(p);

if (myWhereSQL.Length > 0)

{

myWhereSQL.Append(” and “);

}

KeyProperties.Add(p);

if (bolNamedParameter)

{

string pName = “Key” + p.Name;

KeyParameterNames.Add(pName);

myWhereSQL.Append(FixFieldName(FieldName) + ” = @” + pName + ” “);

}

else

{

myWhereSQL.Append(FixFieldName(FieldName) + ” = ? “);

}

}//for

myWriter.WriteLine(“public override int FillDeleteCommand( System.Data.IDbCommand cmd , object objRecord )”);

myWriter.BeginGroup(“{“);

if (KeyProperties.Count == 0)

{

myWriter.WriteLine(“throw new NotSupportedException(\”FillDeleteCommand\”);”);

}

else

{

myWriter.WriteLine(“if( cmd == null ) throw new ArgumentNullException(\”cmd\”);”);

myWriter.WriteLine(“if( objRecord == null ) throw new ArgumentNullException(\”objRecord\”);”);

myWriter.WriteLine(RecordType.FullName + ” myRecord = objRecord as “ + RecordType.FullName + ” ;”);

myWriter.WriteLine(“if( myRecord == null ) throw new ArgumentException(\”must type ‘” + RecordType.FullName + “‘ \”);”);

System.Text.StringBuilder myDeleteSQL = new System.Text.StringBuilder();

myDeleteSQL.Insert(0, “Delete From “ + TableName + ” Where “ + myWhereSQL.ToString());

myWriter.WriteLine(“cmd.Parameters.Clear();”);

myWriter.WriteLine(“cmd.CommandText = @\”” + myDeleteSQL.ToString() + “\” ;”);

myWriter.WriteLine(“System.Data.IDbDataParameter parameter = null ;”);

int index = 0;

foreach (System.Reflection.PropertyInfo p in KeyProperties)

{

myWriter.WriteLine(“”);

myWriter.WriteLine(“parameter = cmd.CreateParameter();”);

WriteSetParameterValue(p, myWriter);

if (bolNamedParameter)

{

myWriter.WriteLine(“parameter.ParameterName = \”” + KeyParameterNames[index] + “\”;”);

}

myWriter.WriteLine(“cmd.Parameters.Add( parameter );”);

index++;

}

myWriter.WriteLine(“”);

myWriter.WriteLine(“return “ + KeyProperties.Count + ” ;”);

}

myWriter.EndGroup(“)”);

在这段代码中,首先是遍历实体类型中所有的绑定到字段的属性,根据其附加的BindFieldAttribute特性 的Key值找到所 有的关键字段属性对象,并创建了一个“关键字段名1=@Key属性名1 And 关键字段名2=@Key属性名2(若未启用命名参数则为“关键字段名1=? And 关 键字段名2=? ……)格式的字符串,该字符串就是SQL语句的Where子语句了,若启用命名参数则生成的文本为。

若没有找到任何关键属性,则无法确定删除记录使用的查询条件,此时代码生成器就会输出抛出异常的代 码。在这里代码生成器不会直接抛出异常而导致ORM框架过早的报警,未来可能有开发人员定义的实体类型只是为了查询或者新增数据库记录,那时不需要定义 关键属性。若对这种实体类型过早的报警则减少了快速ORM框架的使用范围。

若实体类型定义了一个或者多个关键属性,则开始输出代码文本,首先输出检查参数的代码文本,然后遍历 所有的关键属性对象,生成向数据库命令对象添加参数的代码。

这里还用到了一个WriteSetParamterValue的方法用于书写设置参数值的过程,其代码为

private void WriteSetParameterValue( System.Reflection.PropertyInfo p , IndentTextWriter myWriter )

{

if (p.PropertyType.Equals(typeof(DateTime)))

{

BindFieldAttribute fa = (BindFieldAttribute)Attribute.GetCustomAttribute(

p, typeof(BindFieldAttribute));

if (fa.WriteFormat == null || fa.WriteFormat.Length == 0)

{

myWriter.WriteLine(“parameter.Value = myRecord.” + p.Name + “.ToString(\”yyyy-MM-dd HH:mm:ss\”);”);

}

else

{

myWriter.WriteLine(“parameter.Value = myRecord.” + p.Name + “.ToString(\”” + fa.WriteFormat + “\”);”);

}

}

else if (p.PropertyType.Equals(typeof(string)))

{

myWriter.WriteLine(“if( myRecord.” + p.Name + ” == null || myRecord.” + p.Name + “.Length == 0 )”);

myWriter.WriteLine(”   parameter.Value = System.DBNull.Value ;”);

myWriter.WriteLine(“else”);

myWriter.WriteLine(”   parameter.Value = myRecord.” + p.Name + ” ;”);

}

else

{

myWriter.WriteLine(“parameter.Value = myRecord.” + p.Name + ” ;”);

}

}

该方法内判断若属性数据类型为时间型则设置输出的数据格式,若为字符串类型,则判断数据是否为空,若 为空则设置参数值为DBNull

生成更新数据的代码

基础类型RecordORMHelper预留了FillUpdateCommand函数,快速ORM框架更新数据库时首先创建一个数据库命令对象,然后调用FillUpdateCommand函数设置SQL语句,添加SQL参数,然后执行该命令对象接口更新数 据库记录。对于DB_Employees,其FillUpdateCommand函数的代码为

public override int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord )

{

if( cmd == null ) throw new ArgumentNullException(“cmd”);

if( objRecord == null ) throw new ArgumentNullException(“objRecord”);

MyORM.DB_Employees myRecord = objRecord as MyORM.DB_Employees ;

if( myRecord == null ) throw new ArgumentException(“must type ‘MyORM.DB_Employees’ “);

cmd.CommandText = @”Update Employees Set EmployeeID = ?  , LastName = ?  , FirstName = ?  , Title = ?  , TitleOfCourtesy = ?  , Address = ?  , BirthDate = ?  , City = ?  , Country = ?  , EducationalLevel = ?  , EMail = ?  , Extension = ?  , Goal = ?  , HireDate = ?  , HomePage = ?  , HomePhone = ?  , Notes = ?  , PostalCode = ?  , Region = ?  , ReportsTo = ?  , Sex = ?  Where EmployeeID = ? “ ;

cmd.Parameters.Clear();

System.Data.IDbDataParameter parameter = null ;

parameter = cmd.CreateParameter();

parameter.Value = myRecord.EmployeeID ;

cmd.Parameters.Add( parameter );

parameter = cmd.CreateParameter();

parameter.Value = myRecord.BirthDate.ToString(“yyyy-MM-dd”);

cmd.Parameters.Add( parameter );

为其他属性值添加SQL参数对象。。。。。。

parameter = cmd.CreateParameter();

parameter.Value = myRecord.Sex ;

cmd.Parameters.Add( parameter );

//这里为查询条件添加参数

parameter = cmd.CreateParameter();

parameter.Value = myRecord.EmployeeID ;

cmd.Parameters.Add( parameter );

return 22 ;

}

这段代码结构比较简单,首先是对参数进行判断,然后设置SQL更新语句,然后将所有的属性的值依次添 加到SQL参数列 表中,最后还为查询将EmployeeID值添加到SQL参数列表中。

在代码生成器中生成FillUpdateCommand代码文本的代码为

myWriter.WriteLine(“public override int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord )”);

myWriter.BeginGroup(“{“);

if (KeyProperties.Count == 0)

{

myWriter.WriteLine(“throw new NotSupportedException(\”FillUpdateCommand\”);”);

}

else

{

myWriter.WriteLine(“if( cmd == null ) throw new ArgumentNullException(\”cmd\”);”);

myWriter.WriteLine(“if( objRecord == null ) throw new ArgumentNullException(\”objRecord\”);”);

myWriter.WriteLine(RecordType.FullName + ” myRecord = objRecord as “ + RecordType.FullName + ” ;”);

myWriter.WriteLine(“if( myRecord == null ) throw new ArgumentException(\”must type ‘” + RecordType.FullName + “‘ \”);”);

// 更新用SQL语句文本

System.Text.StringBuilder myUpdateSQL = new System.Text.StringBuilder();

// 所有的SQL参数名称

System.Collections.ArrayList ParameterNames = new System.Collections.ArrayList();

foreach (System.Reflection.PropertyInfo p in ps)

{

if (p.CanRead == false)

{

continue;

}

string FieldName = this.GetBindFieldName(p);

if (myUpdateSQL.Length > 0)

{

myUpdateSQL.Append(” , “);

}

if (bolNamedParameter)

{

string pName = “Value” + p.Name;

ParameterNames.Add( pName );

myUpdateSQL.Append(FixFieldName(FieldName) + ” = @” + pName);

}

else

{

myUpdateSQL.Append(FixFieldName(FieldName) + ” = ? “);

}

}//foreach

ParameterNames.AddRange(KeyParameterNames);

myUpdateSQL.Insert(0, “Update “ + FixTableName(TableName) + ” Set “);

myUpdateSQL.Append(” Where “ + myWhereSQL.ToString());

myWriter.WriteLine(“”);

myWriter.WriteLine(“cmd.CommandText = @\”” + myUpdateSQL.ToString() + “\” ;”);

myWriter.WriteLine(“cmd.Parameters.Clear();”);

myWriter.WriteLine(“System.Data.IDbDataParameter parameter = null ;”);

myWriter.WriteLine(“”);

System.Collections.ArrayList ps2 = new System.Collections.ArrayList();

ps2.AddRange(ps);

ps2.AddRange(KeyProperties);

foreach (System.Reflection.PropertyInfo p in ps2)

{

if (p.CanRead == false)

{

continue;

}

myWriter.WriteLine(“”);

myWriter.WriteLine(“parameter = cmd.CreateParameter();”);

WriteSetParameterValue(p, myWriter);

if (bolNamedParameter)

{

// 设置SQL命令对象的名称

myWriter.WriteLine(“parameter.ParameterName = \”” + ParameterNames[0] + “\”;”);

ParameterNames.RemoveAt(0);

}

myWriter.WriteLine(“cmd.Parameters.Add( parameter );”);

}//foreach

myWriter.WriteLine(“”);

myWriter.WriteLine(“return “ + ps2.Count + ” ;”);

}//else

myWriter.EndGroup(“)//public override int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord )”);

这里的KeyPropertiesKeyParameterNamesmyWhereSQL的值都在生成FillDeleteCommand时已经设置好了,这里直接拿来用。若KeyProperties没有内容,说明实体类型没有指明绑定了关键字段的属性,此时无法生成更新时的查询语句,于是输出抛 出异常的C#代码 文本。

我们首先遍历实体类型中所有的绑定了字段的属性对象,拼凑出“Update TableName Set 字段名1=@Value属性名1 , 字段名2=@Value属性名2”(若未启用命名参数则输出为“Update TableName Set 字段名1=? , 字段 名2=?”)样式的SQL文本,然后加上myWhereSQL中的查询条件文本,从而得出了完整的SQL语句,然后将其输出到代码文本中。

我们有一次遍历实体类型所有绑定了字段的属性对象,对于每一个属性输出添加SQL参数对象的C#代码文本。此外还遍历KeyProperties来生成添加查询 条件SQL参数的C#代码文本。

函数最后返回添加的SQL参数个数的返回语句。

生成完整的C#源代码文本

在实现了生成读取数据,插入数据,删除数据和更新数据的代码文本的程序代码后,我们就可以实现完整的 生成C#代码文本 的程序代码了,这些程序代码就是方法GenerateCode的全部内容,其代码为

private string GenerateCode( string nsName , string strFileName , System.Collections.ArrayList RecordTypes )

{

// 开始创建代码

IndentTextWriter myWriter = new IndentTextWriter();

myWriter.WriteLine(“using System;”);

myWriter.WriteLine(“using System.Data;”);

myWriter.WriteLine(“namespace “ + nsName);

myWriter.BeginGroup(“{“);

// 对每一个数据容器对象创建数据处理类的代码

foreach (Type RecordType in RecordTypes)

{

string TableName = RecordType.Name;

BindTableAttribute ta = (BindTableAttribute)Attribute.GetCustomAttribute(

RecordType, typeof(BindTableAttribute), false);

if (ta != null)

{

TableName = ta.Name;

}

if (TableName == null || TableName.Trim().Length == 0)

{

TableName = RecordType.Name;

}

TableName = TableName.Trim();

System.Reflection.PropertyInfo[] ps = this.GetBindProperties(RecordType);

myWriter.WriteLine(“public class “ + RecordType.Name + “ORMHelper : “ + typeof(RecordORMHelper).FullName);

myWriter.BeginGroup(“{“);

myWriter.WriteLine(“”);

myWriter.WriteLine(“///<summary>创建对象</summary>”);

myWriter.WriteLine(“public “ + RecordType.Name + “ORMHelper(){}”);

myWriter.WriteLine(“”);

生成重载TableName的代码

生成重载RecordFieldNames的代码

生成重载FillUpdateCommand的代码

生成重载FillDeleteCommand的代码

生成重载FillInsertCommand的代码

生成重载InnerReadRecord的代码

}//foreach

myWriter.EndGroup(“}//namespace”);

// 若需要保存临时生成的C#代 码到指定的文件

if (strFileName != null && strFileName.Length > 0)

{

myWriter.WriteFile(strFileName, System.Text.Encoding.GetEncoding(936));

}

return myWriter.ToString();

}

这个函数的参数是生成的代码的名称空间的名称,保存代码文本的文件名和要处理的数据库实体对象类型列 表。在函数中首先创建一个myWriter的代码文本书写器,输出导入名称空间的代码文本,输出命名空间的代码文本,然后遍历RecordTypes列表中的所有的实体 对象类型,对每一个实体对象类型输出一个定义类的C#代码文本,类名就是 类 型名称+ORMHelper,该类继承自RecordORMHelper类型。然后执行上述的生成TableNameRecordFieldNamesFillUpdateCommandFillDelteCommandFillInsertCommandInnerReadRecordC#代码文本的过程,这样就完成了针对一个实体对象类型的C#代码的生成过程。

当代码生成器完成工作后,内置的代码文本书写器myWriter中就包含了完整的C#代码文本。这个代码文本中包含了多个从RecordORMHelper类型派生的数据库操作帮助类型。这样我们就可以随即展开动态编译的操作了。

动态编译

在代码生成器成功的生成所有的C#源代码文本后,我们就可以执行动态编译了,函数MyFastORMFramework.BuildHelpers就是实现动态编译,其代码为

private int BuildHelpers( string strFileName )

{

System.Collections.ArrayList RecordTypes = new System.Collections.ArrayList();

foreach( Type RecordType in myRecordHelpers.Keys )

{

if( myRecordHelpers[ RecordType ] == null )

{

RecordTypes.Add( RecordType );

}

}//foreach

if( RecordTypes.Count == 0 )

return 0 ;

// 开始创建代码

string nsName = “Temp” + System.Guid.NewGuid().ToString(“N”);

// 生成C#代码

string strSource = GenerateCode(nsName, strFileName , RecordTypes );

// 编译临时生成的C#代码

System.Collections.Specialized.StringCollection strReferences = new System.Collections.Specialized.StringCollection();

System.CodeDom.Compiler.CompilerParameters options = new System.CodeDom.Compiler.CompilerParameters();

options.GenerateExecutable = false;

options.GenerateInMemory = true ;

// 添加编译器使用的引用

System.Collections.ArrayList refs = new System.Collections.ArrayList();

foreach( Type t in RecordTypes )

{

refs.Add( t.Assembly.CodeBase );

}

refs.Add( this.GetType().Assembly.CodeBase );

refs.AddRange( new string[]{

“mscorlib.dll”,

“System.dll” ,

“System.Data.dll” ,

});

for( int iCount = 0 ; iCount < refs.Count ; iCount ++ )

{

string strRef = ( string ) refs[ iCount ] ;

if( strRef.StartsWith(“file:///”))

strRef = strRef.Substring( “file:///”.Length );

if( options.ReferencedAssemblies.Contains( strRef ) == false )

{

options.ReferencedAssemblies.Add( strRef );

}

}

//string strSource = myWriter.ToString();

// 调用C#代码编译器编译生成 程序集

Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

// 若使用微软.NET框架.1则调用ICodeCompiler

//System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler();

//System.CodeDom.Compiler.CompilerResults result = compiler.CompileAssemblyFromSource( options , strSource );

// 若使用VS.NET2005或 更新版本编译程序会在这里形成一个编译警告信息,

// 则可以将上面两行代码去掉而使用下面的代码

System.CodeDom.Compiler.CompilerResults result = provider.CompileAssemblyFromSource(options, strSource);

if( result.Errors.Count == 0 )

{

System.Reflection.Assembly asm = result.CompiledAssembly ;

myAssemblies.Add( asm );

// 创建内置的数据库对象操作对象

foreach( Type RecordType in RecordTypes )

{

Type t = asm.GetType( nsName + “.” + RecordType.Name + “ORMHelper” );

RecordORMHelper helper = ( RecordORMHelper ) System.Activator.CreateInstance( t );

myRecordHelpers[ RecordType ] = helper ;

System.Console.WriteLine(“FastORM\”” + RecordType.FullName + “\”创建操作帮助对象);

}

}

else

{

System.Console.WriteLine(“ORM框架动态编 译错误 );

foreach( string strLine in result.Output )

{

System.Console.WriteLine( strLine );

}

}

provider.Dispose();

return RecordTypes.Count ;

}

在本函数中,我们遍历实体Lexington注册列表,找到所有没有装备数据库操作帮助器的实体类型,添加到RecordTypes列表中,然后调用GenerateCode函数生成C#代码。

我们确定编译过程要引用的程序集,Mscorlib.dllSystem.dllSystem.Data.dll是基本的必不可少的引用,所有的参与动态编译的实体对象类型所在的程序集也得引用,快速ORM框架本身所在的程序集也得引用。将所 有的引用信息添加到optionsReferencedAssemblies列表中,这里的options变量是编译使用的参数。然后我们使用myWriter.ToString()获得代码生成器生成的C#源代码文本。我们创建一个CSharpCodeProvider对象,准备编译了,对于微软.NET框架1.12.0其调用过程是不同的。对于微软.NET框架1.1,其调用过程为

Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler();

System.CodeDom.Compiler.CompilerResults result = compiler.CompileAssemblyFromSource( options , strSource );

而对微软.NET框架2.0其调用过程为

Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

System.CodeDom.Compiler.CompilerResults result = provider.CompileAssemblyFromSource(options, strSource);

这体现了微软.NET框架1.12.0之间的差别。但微软.NET框架2.0是兼容1.1的,因此用于微软.NET1.1的代码可以在微软.NET2.0下编译通过,但编译器会提示警告信息。

这里调用CompileAssemblyFromSource实际上就是调用微软.NET框架中的基于命令行的C#程序编译器csc.exe的封装。其内部会根据编译器参数options保存的信息生成命令行文本然后启动csc.exe进程。然后将csc.exe的输出结果保存在CompilerResults对象中。

若一切顺利,则使用CompilerResults.CompiledAssembly就能获得编译后生成的程序集,然后我们使用反射操作,对每一个实体类型从动态编译生成的程序集中获 得对应的数据库帮助器的类型,然后使用System.Activator.CreateInstance函数就能实例化一个数据库操作帮助器,将这个帮助器放置在实体类型注册列表中等待下次选用。

操作数据库

我们使用动态编译技术获得了数据库操作帮助器,现在我们就使用这些帮助器来实现高速的ORM操作。

查询数据 ReadObjects

快速ORM框架中,定义了一个ReadObjects的函数,用于从数据库中读取数据并生成若干个实体对象,其代码为

public System.Collections.ArrayList ReadObjects( string strSQL , Type RecordType )

{

this.CheckConnection();

if( strSQL == null )

{

throw new ArgumentNullException(“strSQL”);

}

if( RecordType == null )

{

throw new ArgumentNullException(“RecordType”);

}

RecordORMHelper helper = this.GetHelper( RecordType );

using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())

{

cmd.CommandText = strSQL ;

System.Data.IDataReader reader = cmd.ExecuteReader();

System.Collections.ArrayList list = helper.ReadRecords( reader , 0 );

reader.Close();

return list ;

}

}

private void CheckConnection()

{

if( myConnection == null )

{

throw new InvalidOperationException(“Connection is null”);

}

if( myConnection.State != System.Data.ConnectionState.Open )

{

throw new InvalidOperationException(“Connection is not opened”);

}

}

这个函数的参数是SQL查询语句和实体对象类型。在这个函数中,首先是调用CheckConnection函数来检查数 据库的连接状态,然后使用GetHelper函数获得对应的数据库操作帮助类,然后执行SQL查询,获得一个数据库读取器,然后调用数据操作帮助类的ReadRecords获得一个列表,该列表 就包含了查询数据所得的实体对象。这个过程没有使用反射,执行速度非常快,使用这个快速ORM框架,执行速度跟我们传统的手工编写代码创建实体对象的速度是一样的,但大大降低了我们的开发工作 量。

在快速ORM框架中,根据ReadObjects函数派生了ReadObjectReadAllObject等系列读取数据的函数,其原理都是一样的。

删除数据 DeleteObject

在快速ORM框架中定义了一个DeleteObject函数用于删除数据,其代码为

public int DeleteObject( object RecordObject )

{

this.CheckConnection();

if( RecordObject == null )

{

throw new ArgumentNullException(“RecordObject”);

}

RecordORMHelper helper = this.GetHelper( RecordObject.GetType() );

using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())

{

if( helper.FillDeleteCommand( cmd , RecordObject ) > 0 )

{

return cmd.ExecuteNonQuery();

}

}

return 0 ;

}

这个函数的参数就是要删除的对象,在函数中,首先调用GetHelper函数获得数据操作帮助器, 然后创建一个数据库命令对象,调用帮助类的FillDeleteCommand函数初始化数据库命令对象,然后执行该命令对象即可删除数据,过程简单明了。ORM框架还定义了DeleteObjects函数用于删除多 个实体对象,其原理和DeleteObject函数一样。

更新数据 UpdateObject

快速ORM框架定义了UpdateObject函数用于更新数据,其代码为

public int UpdateObject( object RecordObject )

{

this.CheckConnection();

if( RecordObject == null )

{

throw new ArgumentNullException(“RecordObject”);

}

RecordORMHelper helper = this.GetHelper( RecordObject.GetType());

using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())

{

int fields = helper.FillUpdateCommand( cmd , RecordObject );

if( fields > 0 )

{

return cmd.ExecuteNonQuery();

}

}

return 0 ;

}

过程很简单,首先使用GetHelepr函数获得数据库帮助器,然后调用它的FillUpdateCommand函数来设置数据库命令对象,然后执行数据库命令对象即可完成删除数据的操作。ORM框架还定义了 UpdateObjects函数用于更新 多条数据库记录,其原理和UpdateObject函数是一样的。

新增数据 InsertObject

快速ORM框架定义了InsertObject函数用于新增数据库记录,其代码为

public int InsertObject( object RecordObject )

{

this.CheckConnection();

if( RecordObject == null )

{

throw new ArgumentNullException(“RecordObject”);

}

RecordORMHelper helper = this.GetHelper( RecordObject.GetType());

using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())

{

int fields = helper.FillInsertCommand( cmd , RecordObject );

if( fields > 0 )

{

return cmd.ExecuteNonQuery();

}

}//using

return 0 ;

}

这个函数也很简单,使用GetHelper获得数据库帮助器,调用帮助器的FillInsertCommand函数设置数据库命令对象,然后执行它即可向数据库插入一条记录。另外一个InsertObjects函数用于插入多 条数据库记录,其原理是一样的。

使用ORM框架

在这里我们建立一个简单的WinForm程序来测试使用快速ORM框架。首先我们在一个Access数据库中建立一个员工信息表,名称为Empolyees,并相应的定义了一个数据库实体类型DB_Employees。然后画出一个窗口放置一些控件,编写一些代码,运行程序,其运行界面为

该演示程序主要代码为

/// <summary>

/// 连接数据库,创建快速ORM框架对象

/// </summary>

/// <returns>ORM框架对象</returns>

private MyFastORMFramework CreateFramework()

{

System.Data.OleDb.OleDbConnection conn = new System.Data.OleDb.OleDbConnection();

conn.ConnectionString = “Provider=Microsoft.Jet.OLEDB.4.0;Data Source=” + System.IO.Path.Combine( System.Windows.Forms.Application.StartupPath , “demomdb.mdb” );

conn.Open();

return new MyFastORMFramework( conn );

}

// 刷新按钮事件处理

private void cmdRefresh_Click(object sender, System.EventArgs e)

{

using( MyFastORMFramework myWork = this.CreateFramework())

{

RefreshList( myWork );

}

}

// 用户名列表当前项目改变事件处理

private void lstName_SelectedIndexChanged(object sender, System.EventArgs e)

{

DB_Employees obj = lstName.SelectedItem as DB_Employees ;

if( obj != null )

{

this.txtID.Text = obj.EmployeeID.ToString() ;

this.txtName.Text = obj.FullName ;

this.txtTitleOfCourtesy.Text = obj.TitleOfCourtesy ;

this.txtAddress.Text = obj.Address ;

this.txtNotes.Text = obj.Notes ;

}

else

{

this.txtID.Text = “”;

this.txtName.Text = “”;

this.txtTitleOfCourtesy.Text = “”;

this.txtNotes.Text = “” ;

this.txtAddress.Text = “”;

}

}

// 新增按钮事件处理

private void cmdInsert_Click(object sender, System.EventArgs e)

{

try

{

using( dlgRecord dlg = new dlgRecord())

{

dlg.Employe = new DB_Employees();

if( dlg.ShowDialog( this ) == DialogResult.OK )

{

using( MyFastORMFramework myWork = this.CreateFramework())

{

if( myWork.InsertObject( dlg.Employe ) > 0 )

{

RefreshList( myWork );

}

}

}

}

}

catch( Exception ext )

{

MessageBox.Show( ext.ToString());

}

}

// 删除按钮事件处理

private void cmdDelete_Click(object sender, System.EventArgs e)

{

DB_Employees obj = this.lstName.SelectedItem as DB_Employees ;

if( obj != null )

{

if( MessageBox.Show(

this ,

是否删除 + obj.FullName + 的纪录?”,

系统提示 ,

System.Windows.Forms.MessageBoxButtons.YesNo ) == DialogResult.Yes )

{

using( MyFastORMFramework myWork = this.CreateFramework())

{

myWork.DeleteObject( obj );

RefreshList( myWork );

}

}

}

}

// 刷新员工名称列表

private void RefreshList( MyFastORMFramework  myWork )

{

object[] objs = myWork.ReadAllObjects(typeof( DB_Employees ));

System.Collections.ArrayList list = new ArrayList();

list.AddRange( objs );

this.lstName.DataSource = list ;

this.lstName.DisplayMember = “FullName”;

}

// 修改按钮事件处理

private void cmdEdit_Click(object sender, System.EventArgs e)

{

DB_Employees obj = this.lstName.SelectedItem as DB_Employees ;

if( obj == null )

return ;

using( dlgRecord dlg = new dlgRecord())

{

dlg.txtID.ReadOnly = true ;

dlg.Employe = obj ;

if( dlg.ShowDialog( this ) == DialogResult.OK )

{

using( MyFastORMFramework myWork = this.CreateFramework())

{

if( myWork.UpdateObject( obj ) > 0 )

{

RefreshList( myWork );

}

}

}

}

}

这段代码是比较简单的,而实体类型DB_Employees的代码可以很容易的使用代码生成器生成出来。借助于快速ORM框架,使得基本的数据库记录维护操作开 发速度快,运行速度也快。

部署快速ORM框架

这个快速ORM框架是轻量级的,你只需要将MyFastORMFramework.cs以及BindTableAttributeBindFieldAttribute的代码复制到你的C#工程即可,也可将它们编译成一个DLL,供VB.NET等其他非C#.NET工程使用。

小结

在本课程中,我们使用反射和动态编译技术实现了一个快速ORM框架,详细学习了一个比较简单的动态编 译技术的完整实现。动态编译技术将自由灵活和运行速度结合在一起,是一个比较强大的软件开发技术,综合利用反射和动态编译技术使得我们有可能打造灵活而高 速的程序框架。

[转载]asp.net mvc + JqueryValidate(二)

mikel阅读(941)

[转载]asp.net mvc + JqueryValidate(二) – { TT.Net } – 博客园.

上一篇 Asp.net MVC +JQueryValidation + AjaxForm 里简单完成了一下数据的效验

如果下载DEMO试过的朋 友可能发现MyModel里定义的DataType=Url和DataType=EMailAddress的验证是没有生效的

这里来补 充一下

这次就不做DEMO了,就2段代码加入一下就可以了

新建一个 DataTypeValidator.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace JqueryValidate
{
    public class DataTypeValidator : DataAnnotationsModelValidator<DataTypeAttribute>
    {
        private readonly string message;

        public DataTypeValidator(ModelMetadata metadata, ControllerContext context, DataTypeAttribute attribute)
            : base(metadata, context, attribute)
        {
            message = attribute.ErrorMessage;
        }

        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            var rules = new List<ModelClientValidationRule>();
            ModelClientValidationRule rule;
            switch (Attribute.DataType)
            {
                case DataType.EmailAddress:
                    rule = new ModelClientValidationRule {ErrorMessage = message, ValidationType = "email"};
                    rule.ValidationParameters.Add("true", "true");
                    rules.Add(rule);
                    break;
                case DataType.Url:
                    rule = new ModelClientValidationRule {ErrorMessage = message, ValidationType = "url"};
                    rule.ValidationParameters.Add("true", "true");
                    rules.Add(rule);
                    break;
                //case DataType.Date:
                //    rule = new ModelClientValidationRule {ErrorMessage = message, ValidationType = "date"};
                //    rule.ValidationParameters.Add("true", "true");
                //    rules.Add(rule);
                //    break;
                //case DataType.DateTime:
                //    rule = new ModelClientValidationRule {ErrorMessage = message, ValidationType = "date"};
                //    rule.ValidationParameters.Add("true", "true");
                //    rules.Add(rule);
                //    break;
            }
            return rules;
        }
    }
}

这里我把datetime这个注释掉了。因为JQueryvalidate的日期格式是 yyyy/mm/dd这样的,而我前台是yyyy年MM月dd日,而后台是支持这样的格式的,所以这里注释掉就让

HomeController.cs里的

 if (!ModelState.IsValid)
                return JsonError(GetError(ModelState));

来返回json回传的错误吧

image

新建了上面那个继承自

DataAnnotationsModelValidator<DataTypeAttribute

的类后

还在要Global.ascx.cs的Application_Start里注册一下这个 DataAnnotationsModelValidatorProvider

            DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(DataTypeAttribute), typeof(DataTypeValidator));

前台代码不需要修改,URL和EMAIL的前台验证就完成了。

image

image

[转载]清风乱谈 之 基于WCF的电子商务解决方案(一)

mikel阅读(974)

[转载]清风乱谈 之 基于WCF的电子商务解决方案(一) – 清风的.net研究小院 – 博客园.

首先,先用这个图片来说明我上次提到的多个组件服务器与WEB服务器之间的关系

这么做至少有3个明显的好处:

1. 将站点的压力平均化,提高网站的浏览速 度,降低服务器压力。

2. 保证各个应用组件之间逻辑的绝对分离,实 现了我们说的低耦合。

3. 应用服务器的接口可以多次复用,企业可以 通过重新组合应用服务器接口建立统一的电子商务体系。

该程序在VS2008下的结构如 下:

其中Orders对应着 订单管理组件服务系统”,Products对应着“产品目录管 理组件服务系统”,Marketing对应 着“市场营销及促销组件服务系统”,多个组件可以以IIS为宿主分别架设在多台服务器上。

这里是IIS宿 主站点的配置:

以下为WEB服务器对组件服务器 接口的调用方法:

public void UpdateQuantity(Guid productID, int quantity)

{

//此处为对产品目录管理组件服务系统中提供的Product GetProductByID(Guid productID);接口的调用

Product product = ServiceLoader.LoadService<IProduct>().GetProduct(productID);

if (product.Inventory < quantity)

{

throw new BusinessException(“Lack of inventory.”);

}

OrderDetail detail = this.Order.Details.Where(item => item.ProductID == productID).ToArray<OrderDetail>()[0];

detail.Quantity = quantity;

this.View.BindingOrderInfo(this.Order);

}

这段为接口调用在WEB.CONFIG文 件里面的相关配置代码

<client>

<endpoint address=http://localhost/PetShop/Products/productservice.svc behaviorConfiguration=petShopBehavior binding=ws2007HttpBinding contract=Artech.PetShop.Products.Service.Interface.IProductService name=productservice/>

<endpoint address=http://localhost/PetShop/Infrastructures/MembershipService.svc behaviorConfiguration=petShopBehavior binding=ws2007HttpBinding contract=Artech.PetShop.Infrastructures.Service.Interface.IMembershipService name=membershipservice/>

<endpoint address=http://localhost/PetShop/Orders/OrderService.svc behaviorConfiguration=petShopBehavior binding=ws2007HttpBinding contract=Artech.PetShop.Orders.Service.Interface.IOrderService name=orderservice/>

</client>

本次文章重点在把我昨天提出的理论找出一些实际的例子进行演示说明,这个码字和说话毕竟是有点区别 的,希望大家可以理解~

[转载].net框架读书笔记---CLR内存管理\垃圾收集

mikel阅读(1023)

[转载].net框架读书笔记—CLR内存管理\垃圾收集 – 恒星的恒心 – 博客园.

一、垃圾收集平台基本原理解析

C#中程序访问一个资源需要以下步骤:

  • 调用中间语言(IL)中的newobj指令,为表示某个特定资源的类型实例分配一定的内存空间。
  • 初始化上一步所得的内存,设置资源的初始状态,从而使其可以为程序所用。一个类型的实例构造器负责这样的初始化工作。
  • 通过访问类型成员来使用资源。
  • 销毁资源状态,执行清理工作。
  • 释放托管堆上面的内存,该步骤由垃圾收集器全权负责,值类型实例所占的内存位于当前运行线程的堆栈上,垃圾收集器并不负责这些资源的回收,当值类 型实例变量所在的方法执行结束时,他们的内存将随着堆栈空间的消亡而自动消亡,无所谓回收。对于一些表示非托管的类型,在其对象被销毁时,就必须执行一些 清理代码。

当应用程序完成初始化后,CLR将保留(reserve)一块连续的地址空间,这段空间最初并不对应任何的物理内存(backing storage)(该地址是一段虚拟地址空间,所以要真正使用它,它必须为其“提交”物理内存),该地址空间即为托管堆。托管堆上维护着一个指针,暂且称 之为NextObjPtr。该指针标识着下一个新建对象分配时在托管堆中所处的位置。刚开始的时候,NextObjPtr被设为CLR保留地址空间的基地 址。

中间语言指令newObj负责创建新的对象。在代码运行时,newobj指令将导致CLR执行以下操作:

  • 计算类型的所有实例字段(以及其基类型所有的字段)所需要的字节总数。
  • 在前面所的字节总数的基础上面再加上对象额为的附加成员所需的字节数:一个方法指针和一个SyncBlockIndex。
  • CLR检查保留区域中的空间是否满足分配新对象所需的字节数—–如果需要则提交物理内存。如果满足,对象将被分配在NextObjPtr指 针所指的地方。接着类型的实例构造器被调用(NextObjPtr会被传递给this参数),IL指令 newobj(或者说new操作符)返回其分配内存地址。就在newobj指令返回新对象的地址之前,NextObjPtr指针会越过新对象所处的内存区 域,并指示出下一个新建对象在托管堆中的地址。

下图演示了包含A,B,C三个对象的托管堆,如果再分配对象将会被放在NextObjPtr指针所演示的位置(紧跟C之后)

在C语言中堆分配内存时,首先需要遍历一个链表数据结构,一旦找到一个足够大的内存块,该内存块就会被拆开来,同时链表相应节点上的指针会得到 适当的调整。但是对于托管堆来说,分配内存仅仅意味着在指针上增加一个数值—显然要比操作链表的做法快许多,C语言都是在找到自由空间为其对象分配内 存,因此连续创建几个对象,他们将很有可能被分散在地址空间的各个角落。但是在托管堆中,连续分配的对象可以保证它们在内存中也是连续的。

就目前来看托管堆在实现的简单性和速度方面要远优于C语言的运行时中的堆。之所以这样是因为CLR做了大胆的假设—那就是应用程序的地址空 间和存储空间是无限的,显然这是不可能的。托管堆必须应用某种机制来允许这种假设。这种机制就是垃圾回收器。

当应用程序调用new创建对象时,托管堆可能没有足够的地址空间来分配该对象。托管堆通过将对象所需要的字节总数添加到NextObjPtr指 针表示的地址上来检测这种情况。如果得到的结果超出了托管堆的地址空间范围,那么托管堆将被认为已满,这时就需要垃圾收集器。,其实这种描述是过于简单 的,垃圾回收与对象的代龄有着密切的关系,还需继续学习垃圾收集。

二、垃圾收集算法

垃圾收集器通过检查托管堆中是否有应用程序不再使用的对象来回收内存。如果有这样的对象,它们的内存将被回收。那么垃圾收集器是这样知道应用程 序是否正在使用一个对象呢??还得继续学习。

每一个应用程序都有一组根(root),一个根是一个存储位置,其中包含着一个指向引用类型的内存指针。该指针或者指向一个托管堆中的对象,或 者被设置为null。例如所有的全局引用类型变量或静态引用类型都被认为是根。另外,一个线程堆栈上所有引用类型的本地变量或者参数变量也被认为是一个 根。最后,在一个方法内,指向引用类型对象的CPU寄存器也被认为是一个根。

当垃圾收集器开始执行时,它首先假设托管堆中的所有对象都是可收集的垃圾。换句话,垃圾收集器假设应用程序中没有一个根引用着托管堆中的对象。 然后垃圾收集器便利所有的根,构造出一个包含所有可达对象的图。例如,垃圾收集器可能会定位出一个引用托管对象的全局变量。下图展示了分配有几个对象的托 管堆,其中对象A,C,D,F为应用程序的根所直接引用。所有这些对象都是可达对象图的一部分。当对象D被添加到该图中时,垃圾收集器注意到它还引用着对 象H,于是对象H被添加到该图,垃圾回收器就这样子以递归的方式来遍历应用程序中所有的可达对象。

一旦该部分的可达对象完成后,垃圾回收器将检查下一个根,并遍历其引用的对象。当垃圾回收器在对象之间进行遍历时,如果发现某对象已经添加到可 达对象图中时(比如上图中的H,在检查D的时候已经将其添加到了可达对象图),它会停止沿着该对象标识的路径方向上遍历的活动。两个目的:

  • 可以避免垃圾收集器对一些对象的多次遍历,可高性能。
  • 如果两个对象之间出现了循环引用,可以避免遍历陷入无限循环(比如上图中D引用着H,而H又引用着D)。

垃圾收集器一旦检查完所有的根,其得到的可达对象将包含所有从应用程序的根可以访问的对象。任何不在该图中的对象将是应用程序 不可访问的对象,不可达的对象,因此也是可以被执行垃圾收集器的对象。垃圾收集器接着线性地遍历托管堆以寻找包含可收集垃圾对象的连续区域。

PS:CLR的垃圾收集机制对我来说有点非主流,在此之前,我一直认为是垃圾收集器直接去寻找不可达的对象,现在看来垃圾收集 器使用了逆向思维,通过找到可达对象来找到不可达的对象(这个原因还得继续思考)。

如果找到了较大的连续区域,垃圾收集器将会把内存中的一些非垃圾对象搬移到这些连续区块中以压缩堆栈,显然搬移内存中的对象将 使所有这些指向对象的指针变的无效。所以垃圾收集器必须修改应用程序的根以使它们指向这些对象更新后的位置。另外,如果任何对象包含有指向这些对象的指 针,那么垃圾收集器也会负责矫正它们。托管堆被压缩以后,NextObjPtr指针将被设为指向最后一个非垃圾对象之后。下图展示了对于上面图执行垃圾收 集器后的托管堆。

可见垃圾回收器对于应用程序的性能有不小的影响,CLR采用了代龄等措施来优化了性能(以后学习)。

因为任何不从应用程序的根中访问的对象都会在某个时刻被收集,所以应用程序将不可能发生内存泄漏,另外应用程序也不可能再访问已经被释放的对 象。因为如果对象可达,它将不可能被释放;而如果对象不可达,应用程序必将无法访问到它。

下面代码演示了垃圾收集器是这样分配管理对象的:

代码

class Program { static void Main(string[] args) { //在托管堆上ArrayList对象,a现在就是一个根 ArrayList a = new ArrayList(); //在托管堆上创建10000个对象 for (int i = 0; i < 10000; i++) { a.Add(new Object());//对象被创建在托管堆上 } //现在a是一个根(位于线程堆栈上)。所以a是一个可达对象 //,a引用的10000个对象也是可达对象 Console.WriteLine(a.Count); //在a.Count返回后,a便不再被Main中的代码所引用, //因此也就不再是一个根。如果另外一个线程在a.Count的结果被 //传递给WirteLine之前启动了垃圾收集,那么a以及它所引用的10000个对象将会被回收。 //上面for里面的变量i虽然在后面的代码中不再被引用,但由于它是一个值类型,并不存在于 //托管堆中,所以它不受垃圾收集器的管理,它在Main方法执行完毕后会随着堆栈的消失而自动 //被系统回收 Console.WriteLine("End of method"); } }

CLR之所以能够使用垃圾回收机制,有一个原因是因为托管堆总是能知道一个对象的实际类型,从而使用其元数据信息来判断一个对象的那些成员引用 着其他对象。

欢迎转载,请注明出处 贺爱平 http://www.cnblogs.com/heaiping

[转载]jQuery第六课:实现一个Ajax的TreeView

mikel阅读(1089)

[转载]jQuery第六课:实现一个Ajax的TreeView – 技术 理论 及其它 – 博客园.

TreeView是ASP.NET自带的控件,不过自带的控件在灵活性上有诸多限制。在JQuery的帮助下,自己实现一个TreeView也不困 难。本文是前几篇文章所讲内容的一个综合演练。最终实现的效果是一个目录文件查看器,如图所示:

image

其原理是,当用户单击一个目录的时候,将这个 目录的路径发送给服务器端,服务器端返回这个目录中的文件和目录信息。在服务器端,定义一个如下的类来表示要传递的文件信息:

public class FileInformation
{
    public string FullPath
    {
        get;        set;
    }
    public string Name
    {
        get;        set;
    }
    public string Info
    {
        get;        set;
    }
    public bool IsFolder
    {
        get;        set;
    }
}

其中FullPath是文件的完整路径,用于获取它的子文件夹/文件用,Name是文件的名字,用于显示,IsFolder是区分这条数据是一个文 件还是文件夹,以便用不同的图标来显示,最后一个Info是一些附加信息,在此例中没有用到。根据一个路径获得目录中的文件信息的C#代码很简单,顺便就 贴在这里:

public class FileManager
{
    public static List<FileInformation> GetFolderContent(string fullpath)
    {
        List<FileInformation> res = new List<FileInformation>();
        DirectoryInfo info = new DirectoryInfo(fullpath);
        if (info.Exists)
        {           
            foreach (DirectoryInfo d in info.GetDirectories())
            {
                res.Add(new FileInformation
                {
                    FullPath = d.FullName, Name = d.Name,IsFolder = true,
                    Info = "Any More Information goes here"
                });
            }
            foreach (FileInfo f in info.GetFiles())
            {
                res.Add(new FileInformation
                {
                    FullPath = f.FullName,Name = f.Name,IsFolder = false,
                    Info = "Any More Information goes here"
                });
            }
        }
        return res;
    }
}

此例中采用JSON数据的格式来传递这些信息。因此要将这些数据序列化。在.Net 3.5中,有现成的将实体类序列化成JSON数据的类,使用方法如下

public static string ToJson<T>(T obj)
    {
        DataContractJsonSerializer d = new DataContractJsonSerializer(typeof(T));
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        d.WriteObject(ms, obj);
        string strJSON = System.Text.Encoding.UTF8.GetString(ms.ToArray());
        ms.Close();
        return strJSON;
    }

如果是.net 2.0,则可以寻找一些第三方的组件,自己写一个也不麻烦。

至此,服务器端的主要工作已经完成了。新建一个Genric Handler文件,filelist.ashx,代码如下,简单的响应下请求,输出数据即可:

public class FileList : IHttpHandler {    
    public void ProcessRequest (HttpContext context) {
        string path = context.Request.QueryString["path"];
        string data = JsonHelper.ToJson<List<FileInformation>>(FileManager.GetFolderContent(path));
        context.Response.Write(data);
    } 
    public bool IsReusable {
        get {
            return false;
        }
    }
}

下面考虑客户端html代码的编写。最主要的就是两个事件,也就是鼠标点击发送ajax请求,处理返回的json数据生成html代码,鼠标再次点 击将html代码清空。在这里,采用ul li来显示这个treeview,在li中有一个不可见的span,里面包含了文件完整的路径,它将用作发起ajax请求的参数,但是对用户是不可见的。

HTML代码很简单,就4行:

<body>
<ul>
</ul>
</body>

首先需要初始化一个根目录,例如D:,代码如下:

$(function() {
            $('<li class="folder">D:\\<span class="fullpath">D:\\</span></li>').appendTo('ul');

            $('li').hover(function() {
                $(this).css('cursor', 'pointer');
            },
                function() { $(this).css('cursor', 'default'); });

            $('li.folder').toggle(LoadFile, CloseFolder);
        });

构造好一个li结点,添加到ul中去,然后设置下鼠标动作的样式,最后为其绑定事件处理程序,LoadFile和CloseFolder。

function LoadFile(event) {
            if (this == event.target) {
                var path = $(this).find('span').html();
                var node = $('<ul>');
                $(this).append(node);
                $.getJSON('filelist.ashx', { path: path }, function(data) {
                    $.each(data, function() {
                        if (this.IsFolder) {
                            node.append($('<li>').addClass('folder').html(this.Name).append($('<span>').addClass('fullpath').html(this.FullPath)));
                        }
                        else {
                            node.append($('<li>').addClass('file').html(this.Name).append($('<span>').addClass('fullpath').html(this.FullPath)));
                        }
                    });
                    node.find('li.folder').toggle(LoadFile, CloseFolder);
                });                 
            }
        }

首先要判断event的target和 this是否是同一个对象,以避免点击子节点事件浮升的时候造成多次触发。首先利用find和html函数获得完整的路径。构造好一个ul节点并把它添加 到当前的li中。此时ul是空的,接下来发起ajax请求,获得服务器端的数据。对每条数据生成一个li,其中对于是否是目录加以判断,生成带有不同 class的li,再加到node中。最后,不要忘记为新增的节点也绑定事件处理程序。代码还是比较简单的,至于关闭目录节点的代码就更加简单了,

function CloseFolder(event) {
    if (this == event.target)
        $(this).find('ul').remove();
}

至此此范例已经完成了。还少了几句css,不再列出。

这个例子实现的功能和样式都比较粗糙,不过在此基础上做更多的扩展和美化已经不是难事。例如可以加上一点现成的动画效果:

function CloseFolder(event) {
    if (this == event.target) {
        var node = $(this).find('ul');
        node.hide('slow', function() { $(this).find('ul').remove(); });                
    }
}

先隐藏,再删除。类似地,可以加载完毕后立刻隐藏, 再淡出。