[转载]读淘宝HTML有感

mikel阅读(1103)

[转载]【原】读淘宝HTML有感 – FMP-FMP – 博客园.

今天公司的前端让我帮看一下淘宝的样式表是怎么读取的,我看了一下,第一眼感觉有点奇怪:

整个首页仅有一个link标记和一个script标记

<link rel="stylesheet" href="http://a.tbcdn.cn/p/fp/2012/fp/??layout.css,dpl/dpl.css,sitenav/sitenav.css,logo/logo.css,search/search.css,<br>nav/nav.css,product-list/product-list.css,mainpromo/mainpromo.css,attraction/attraction.css,notice/notice.css,status/status.css,interlayer/interlayer.css,<br>cat/cat.css,convenience/convenience.css,act/act.css,expressway/expressway.css,guang/guang.css,hotsale/hotsale.css,helper/helper.css,footer/footer.css,<br>recom_new/recom_new.css,local/local.css,globalshop/globalshop.css,guide/guide.css?t=2012080220120724.css" />
<script src="http://a.tbcdn.cn/??s/kissy/1.2.0/kissy-min.js,p/global/1.0/global-min.js,p/fp/2012/core.js,p/fp/2012/fp/module.js,p/fp/2012/fp/util.js,<br>p/fp/2012/fp/directpromo.js?t=2012080620120724.js" data-fp-timestamp="20120806"></script>

简单的思考之后,基本上想到是怎么处理的了。

其实like标记的href属性、script标记的src属性其实都是链接到一个服务器端页面,将css,js文件名已参数的形式传进去,然后根据参数去读取这些文件,再把读取结果写会客户端。当然,肯定会利用到缓存。

除此之外,我用浏览器调试工具看了一下请求和响应结果,其中包含一项:

Status Code:304 Not Modified   什么意思? 看这里:http://bulaoge.net/topic.blg?dmn=webdev&tid=301677

 

淘宝就是淘宝,细节方面考虑的很周全,那么这么做有哪些好处呢 或者说咱学到了什么呢?

1.为什么一个页面会加载这么多个样式表? 我猜:都是按模块化写的,为了适应页面模块经常增删、调整。

2.为什么要把这些样式表写在一起?我猜:是为了减少请求次数,n个样式表只需请求和相应一次。

3.在加上读取文件缓存、CDN缓存、浏览器缓存。页面相应速度会加快很多。

4.样式和js有修改咋办?大家会看到链接后买年会有一个参数:t=2012080220120724.css  t=2012080620120724.js  ,我想是用来清理缓存用的。

 

淘宝就是淘宝,细致入微。记录下来希望以后开发中可以派上用场。

[转载]Asp.net Web API实战

mikel阅读(1070)

[转载]Asp.net Web API实战 – slmk – 博客园.

工作中需要暴露一些功能,或者说API供其他系统调用,其他系统可能是Winform,WPF,ASP.NET或者是Mobile客户端。当然可以用 Web Service或是WCF来实现,但是自己偏向于尝试新的东西,于是,就打算用ASP.NET Web API来实现,也来体验一把RESTFull框架。初次使用,把大体过程记录下来,美名曰“实战”,实际算是入门指导吧。

Asp.net Web API是通过Http协议将API暴露给客户端,很多东西与ASP.NET MVC很相似,其实她本身就包含在ASP.NET MVC 4里面的。下面开始实战:

  1. 安装Asp.net Mvc 4,从这里安装:http://www.asp.net/mvc/mvc4。安装时间大概半小时的样子。支持VS 2010学习版。

  2. 新建项目,选择“Asp.net MVC 4 Web Application”项目类型,然后选择Web API:

 

 

  3. 重点看一下Controller下面的ValuesController类,这个类暴露了以下API:

public class ValuesController : ApiController
{
// GET api/values
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}

// GET api/values/5
public string Get(int id)
{
return "value";
}

// POST api/values
public void Post(string value)
{
}

// PUT api/values/5
public void Put(int id, string value)
{
}

// DELETE api/values/5
public void Delete(int id)
{
}
}

Asp.net Web API实战

2012-08-09 17:46 by slmk, 811 阅读, 0 评论, 收藏, 编辑

工作中需要暴露一些功能,或者说API供其他系统调用,其他系统可能是Winform,WPF,Asp.net或者是Mobile客户端。当然可以用 Web Service或是WCF来实现,但是自己偏向于尝试新的东西,于是,就打算用Asp.net Web API来实现,也来体验一把RESTFull框架。初次使用,把大体过程记录下来,美名曰“实战”,实际算是入门指导吧。

Asp.net Web API是通过Http协议将API暴露给客户端,很多东西与ASp.net MVC很相似,其实她本身就包含在Asp.net Mvc 4里面的。下面开始实战:

  1. 安装Asp.net Mvc 4,从这里安装:http://www.asp.net/mvc/mvc4。安装时间大概半小时的样子。支持VS 2010学习版。

  2. 新建项目,选择“Asp.net MVC 4 Web Application”项目类型,然后选择Web API:

 

 

  3. 重点看一下Controller下面的ValuesController类,这个类暴露了以下API:

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

熟悉http协议的话,上面的很好懂了。F5运行,我用Chrome浏览器测试:

这里返回的是xml格式的,这是因为Chrome自动添加了”Accept:Application/Xml”请求头(各位如何让Chrome返回Json呢?)。默认返回Json格式。

至于URL是如何路由的,熟悉MVC的应该很清楚了。看看App_Start/RouteConfig.cs,当然我们可以在这里配置自己的路由。

  4. 客户端如何调用服务器端的API:

通常我们使用HttpClient类,可以看看dudu老大写的HttpClient + ASP.NET Web API, WCF之外的另一个选择

我这里演示如何使用简单的WebClient来调用,使用WebClient的原因是:不像HttpClient,它不需要.net framework4.0。

WebClient wc = new WebClient(); wc.Headers.Add("Accept:Application/Json"); //这一行不要,结果是一样的
string json = wc.DownloadString("http://localhost:4391/api/values"); var ss= JavaScriptConvert.DeserializeObject<string[]>(json);

这里Json解析使用的是第三方Newtonsoft.Json,我用的是很老的版本了。这里下载最新版本。

这个第三方库比微软自带的都要好用,而且很强大的,同时支持.net和Sivlerlight,据说速度是最快的。

Web Api是根据客户端的Accept请求头,自动返回相应格式的数据,默认是JSON格式。

  5. 实战结束。:)

[转载]用最简单的例子实现jQuery图片即时上传

mikel阅读(1302)

[转载]用最简单的例子实现jQuery图片即时上传 – Zjmainstay – 博客园.

最近看了一些JQuery即时上传的插件,总算看懂了些门路。现将其最为核心的一部分抽取出来,以期用最简单的例子来说明JQuery图片即时上传的原理。

首先本用例一共包含3个文件:

1、上传面板HTML文件。

2、上传处理PHP文件。

3、jQuery库。

第一、上传面板HTML文件(index.html)。

其主要包含了jQuery库、即时上传所需js、表单和iframe框架。下面是其源码,其中已附详细注释。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
     <title>jQuery Upload Image</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     <meta http-equiv="Content-Language" content="zh-CN" />
     <!--<script type="text/javascript" src="jquery-1.6.2.min.js"></script>-->    <!-- 本地jquery库-->
     <script type="text/javascript" src="http://files.cnblogs.com/Zjmainstay/jquery-1.6.2.min.js"></script>
 </head>
 <body>
 <style>
     #exec_target{display:none;width:0;height:0;}
     #feedback{width:1200px;margin:0 auto;}
     #feedback img{float:left;width:300px;height:300px;}    
 </style>
 <script type="text/javascript">
 $(document).ready(function(){
     //选择文件成功则提交表单
     $("#upload_file").change(function(){
         if($("#upload_file").val() != '') $("#submit_form").submit();
     });
     //iframe加载响应,初始页面时也有一次,此时data为null。
     $("#exec_target").load(function(){
         var data = $(window.frames['exec_target'].document.body).find("textarea").html();
         //若iframe携带返回数据,则显示在feedback中
         if(data != null){
             $("#feedback").append(data.replace(/&lt;/g,'<').replace(/&gt;/g,'>'));
             $("#upload_file").val('');
         }
     });
 });
 </script>
 <form id="submit_form" method="post" action="submit_form_process.php" target="exec_target" enctype="multipart/form-data">
     <input type="file" name="upload_file" id="upload_file">        <!-- 添加上传文件 -->
 </form>
 <iframe id="exec_target" name="exec_target"></iframe>    <!-- 提交表单处理iframe框架 -->
 <div id="feedback"></div>    <!-- 响应返回数据容器 -->
 </body>
 </html>

   第二、上传处理PHP文件(submit_form_process.php)。

    其主要包含了图片文件的简单上传,及返回图片标签,其中存储刚上传的图片。下面是其源码。

<?php
     header('content-type:text/html charset:utf-8');
     //不存在当前上传文件则上传
     if(!file_exists($_FILES['upload_file']['name'])) move_uploaded_file($_FILES['upload_file']['tmp_name'],iconv('utf-8','gb2312',$_FILES['upload_file']['name']));
     //输出图片文件<img>标签
     echo "<textarea><img src='{$_FILES['upload_file']['name']}'/></textarea>";
 //End_php

 总结之原理透析:

    讲到jQuery即时上传,不知道其原理的人第一想法一般都是使用AJAX,我也尝试过。但是,由于上传文件不同于一般数据的POST,它需要表单form的提交来完成。因此,jQuery上传插件也是将的数据转至一个新生的form当中将其提交,而表单的target指向新生的iframe,在iframe中做表单提交后的处理,完成后iframe会重新加载并包含处理结果,通过iframe的load事件便可捕捉并获取处理结果,回传至原表单所在的页面(feedback)中,实现jQuery即时上传的效果。而在表单提交的时候,为了实现选择文件后即时提交表单,则使用了input标签的change事件(index.html L19-21),只要选择了文件,input的值就会发生变化,此时就可以提交表单进行处理了。

软件包下载:最简单的jQuery即时上传示例

演示地址:jQuery即时上传演示

[转载]ArrayList和HashTable妙用

mikel阅读(1035)

[转载]ArrayList和HashTable妙用一 – 浪漫骑士必胜 – 博客园.

ArrayList和HashTable妙用一

今天我们主要研究的问题是ArrayList和HashTable的使用。在这里我们引入集合的概念。如何理解集合呢?我们可以将集合看做是“长度可变的,具有很多方法的数组”

下面我们逐步进行演示

 

图一、ArrayList

  ArrayList.Add()

 Add这个方法可以往里面添加任何东西,如下图所示。

图二、ArrayListAdd

图三、Add方法能遍历

  ArrayList泛型集合,上接开头我们讨论的问题,数组都已经限定好了,而集合是想往里面放什么就可以往里面放什么。也可以这么理解,就是集合的范围比数组的范围大。

  下面演示ArrayList里面的另外一个方法:.AddRange

图四、方法Add和Range的区别

图五、AddRange的遍历

图六、Clear方法

图七、Contains判断集合当中有没有这个元素

图八、IndexOf

图九、Insert

图十、InsertRange

图十一、LastIndexOf

图十二、Remove

图十三、RemoveAt

图十四、RemoveRange

图十五、Reverse反转

图十六、一道练习题

图十七、题目二用insert方法

图十八、题二的方法2用for循环做

  上面我们总结了ArrayList泛型集合的很多的方法。想必这种图解的方法对C#初学者来讲,可能比看枯燥的教材来的更加的实惠。由于篇幅的限制,我们将在下一篇文章当中介绍Hashtable及它当中的方法。

[转载]开源搜索引擎 Iveely Search Engine 0.1.0 的发布

mikel阅读(870)

[转载]开源搜索引擎 Iveely Search Engine 0.1.0 的发布 – Iveely Liu – 博客园.

一直热爱搜索引擎事业,但是它现在并不是我工作。你也许会 问,Iveely是什么?Iveely 是I void everything,enjoy loving you的首字母缩写,表达我对搜索引擎的热爱。目前发布的是0.1.0版本,是一个基本版本,包含了无数的Bugs,您可以在 http://iveelyse.codeplex.com/上 下载源码,任何一个改动的源码,您都可以下载到。但是,当您下载源码后,也许您会痛骂我一顿,这是什么源码,能看懂吗?乱七八糟的,当您有这样的想法的时 候,请原谅我,这些东西都是我业余开发,不足之处,定然数不胜数,但我相信,终有一天您也会感叹,原来并没有那么糟糕。所有源码的开放,都是为了知识的共 享,如果您有好的想法和建议,可以发送邮件至我:liufanping@iveely.com,如果您想参与进来,贡献您的代码,请联系我,一起为社区, 为开源做出点点奉献。

        上图是Iveely Search Engine 0.1.0版本的截图,目前只有文本搜索部分,其余的新闻、视频等还没有完全做好,主要是统一的存储模型没有找到。下面大致说一个项目的基础结构:

       

上面主要分为三大块,刚好对应着Iveely搜索引擎中的三个运行步骤(当您下载程序后的运行方式就是按照上面上个步骤来的)。

1. 找到发布文件夹下的IveelySE.Spider.exe 文件,然后输入你需要爬行的链接。这是A搜索部分。
2 . 当爬行完毕之后,找到文件夹下的IveelySE.Index.exe 然后运行,直到结束。即完成索引。这是B整理部分。
3. 此刻你只需要,运行另外一个文件IveelySE.exe你就可以执行搜索了。搜索的界面你需要部署WebSite的网站,即可。或者你不想这样的话,你 在不运行IveelySE.exe的情况下,直接运行Iveely.Search.exe 可在本地执行搜索。 这是C服务部分。

下面介绍一下整个解决方案中,各个项目的含义。

“IveelySE”, “IveelySE\IveelySE.csproj”, 是搜索服务建立部分,搜索的入口是从这里开始的。

“IveelySE.AI”, “IveelySE.AI\IveelySE.AI.csproj”, 搜索的人工智能部分,类似于专家系统。例如输入101+90=?就是通过这个项目计算出来的。

“IveelySE.Classify”, “IveelySE.Classify\IveelySE.Classify.csproj”,是搜索引擎中文本分类(聚类)的实现部分。

“IveelySE.Common”, “IveelySE.Common\IveelySE.Common.csproj”,是搜索引擎中共用部分的提取。

“IveelySE.Config”, “IveelySE.Config\IveelySE.Config.csproj”, 是搜索引擎中的配置部分,尽可能的配置在这个项目中实现。

“IveelySE.Grammar”, “IveelySE.Grammar\IveelySE.Grammar.csproj”, 是搜索引擎中的语法分析部分,例如:site、filetype等等。

“IveelySE.Index”, “IveelySE.Index\IveelySE.Index.csproj”, 是搜索引擎中的索引部分,原数据的整理主要在这里实现,倒排序等也是。

“IveelySE.Liquidate”, “IveelySE.Liquidate\IveelySE.Liquidate.csproj”,是语言处理部分,例如停用词过滤、敏感词等等的处理。

“IveelySE.Segment”, “IveelySE.Segment\IveelySE.Segment.csproj”, ISE中的分词部分,包含词库分词和隐马尔可夫分词。

“IveelySE.Server”, “IveelySE.Server\IveelySE.Server.csproj”, ISE中关于网络通信传输部分。

“IveelySE.Spider”, “IveelySE.Spider\IveelySE.Spider.csproj”, 这是网络爬虫,即数据搜集部分。

“IveelySE.IDFS”, “IveelySE.IDFS\IveelySE.IDFS.csproj”, 这是分布式文件系统部分,包括MapReduce的实现都将在此(此版本无效)。

“IveelySE.BigData”, “IveelySE.BigData\IveelySE.BigData.csproj”, 这是大数据处理部分(此版本无效)。

“IveelySE.BigData.Common”, “IveelySE.BigData.Common\IveelySE.BigData.Common.csproj”, 大数据处理的共同引用部分(此版本无效)。

“IveelySE.BigData.FileSystem”, “IveelySE.BigData.FileSystem\IveelySE.BigData.FileSystem.csproj”,大数据处理的虚拟文件系统(此版本无效)。

“IveelySE.BigData.MasterNode”, “IveelySE.BigData.MasterNode\IveelySE.BigData.MasterNode.csproj”, 大数据存储的主节点部分(此版本无效)。

“IveelySE.BigData.StoreNode”, “IveelySE.BigData.StoreNode\IveelySE.BigData.StoreNode.csproj”, 大数据存储的子节点部分(此版本无效)。

“IveelySE.Search”, “IveelySE.Search\IveelySE.Search.csproj”, 搜索的本地实现部分,无需Web,只要用于内部调试。

“IveelySE.Web”, “IveelySE.Web\IveelySE.Web.csproj”,搜索的Web端实现,界面采用ask的界面。

这 是Iveely Search Engine的大致一个介绍,当然,IveelySE一定没有你想象中的那么好,因为目前处于0.1.0低版本阶段 ,无数的错误或者Bugs一定会让大 家崩溃,但是请您放心,我们(更多的热爱开源事业的人士)会积极的修改其中的Bug,当然项目中也参杂了其它开源项目的东西,例如:Html分析采用的是 HtmlAgilityPack。希望和更多地朋友一起分享我们的快乐。谢谢大家!项目地址:http://iveelyse.codeplex.com/

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

mikel阅读(1108)

[转载]C#发现之旅第十二讲 基于反射和动态编译的快速ORM框架 – 袁永福 电子病历,医疗信息化 – 博客园.

系列课程说明

    为了让大家更深入的了解和使用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

{

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 人员全名/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

public string FullName

{

get

{

return this.LastName + this.FirstName ;

}

}

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

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段值 EmployeeID/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

private System.Int32 m_EmployeeID = 0 ;

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段值 EmployeeID/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

[BindField("EmployeeID" , Key = true )]

public System.Int32 EmployeeID

{

get

{

return m_EmployeeID ;

}

set

{

m_EmployeeID = value;

}

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段值 LastName/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

private System.String m_LastName = null ;

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段值 LastName/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

[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

{

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 对象操作的数据表名称/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

public abstract string TableName

{

get ;

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据读取器

///读取的数据

public object ReadRecord( System.Data.IDataReader reader )

{

int[] indexs = GetFieldIndexs( reader );

return InnerReadRecord( reader ,indexs );

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据读取器

///允许读取的最大的记录个数,为0则无限制

///读取的数据对象列表

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 &gt; 0 &amp;&amp; list.Count &gt;= MaxRecordCount )

{

break;

}

}//while

return list ;

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据读取器

///字段序号列表

///读取的记录对象

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

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据库命令对象

///实体对象实例

///添加的SQL参数个数

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

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据库命令对象

///实体对象实例

///添加的SQL参数个数

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

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据库命令对象

///实体对象实例

///添加的SQL参数个数

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

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段列表数组/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

protected abstract string[] RecordFieldNames

{

get ;

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///数据读取器

///编号列表

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 &lt; FieldCount ; iCount ++ )

{

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

}

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

{

indexs[ iCount ] = -1 ;

string name = FieldNames[ iCount ] ;

for( int iCount2 = 0 ; iCount2 &lt; FieldCount ; iCount2 ++ )

{

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

{

indexs[ iCount ] = iCount2 ;

break;

}

}

}

for( int iCount = 0 ; iCount &lt; FieldCount ; iCount ++ )

{

string name = reader.GetName( iCount );

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

{

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

{

indexs[ iCount2 ] = iCount ;

}

}

}

return indexs ;

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///字符串集合

///连接所得的大字符串

protected string ConcatStrings( System.Collections.IEnumerable strs )

{

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

foreach( string str in strs )

{

if( myStr.Length &gt; 0 )

{

myStr.Append(",");

}

myStr.Append( str );

}//foreach

return myStr.ToString();

}

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///字段名1

///字段名2

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

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 &gt; 0 )

{

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

}

index = name2.IndexOf(".");

if( index &gt; 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

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

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

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///日期数据

///保存格式化字符串

///转换后的数据

protected object DateTimeToDBValue( DateTime Value , string Format )

{

if( Format != null || Format.Length &gt; 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 &gt;= 0 )

{

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

}

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

if( index &gt;= 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 &lt; 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 &gt;= 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 &amp;&amp; pps.Length &gt; 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 &lt; myValues.Count ; iCount ++ ) { if( iCount &gt; 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 &lt; 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 &amp;&amp; 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 &amp;&amp; fa.WriteFormat.Length &gt; 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 &lt; myValues.Count ; iCount ++ )"); myWriter.BeginGroup("{"); myWriter.WriteLine("if( iCount &gt; 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属性的。

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段值 EmployeeID/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

private System.Int32 m_EmployeeID = 0 ;

///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>/// 字段值 EmployeeID/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

[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 &lt; 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 &gt; 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(")");

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 &gt; 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 )");

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("///

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

<summary>创建对象</summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

");

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

myWriter.WriteLine("");

生成重载TableName的代码

生成重载RecordFieldNames的代码

生成重载FillUpdateCommand的代码

生成重载FillDeleteCommand的代码

生成重载FillInsertCommand的代码

生成重载InnerReadRecord的代码

}//foreach

myWriter.EndGroup("}//namespace");

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

if (strFileName != null &amp;&amp; strFileName.Length &gt; 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 &lt; 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 ) &gt; 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 &gt; 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 &gt; 0 )

{

return cmd.ExecuteNonQuery();

}

}//using

return 0 ;

}

 

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

使用ORM框架

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

该演示程序主要代码为

///

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

///

</summary>

&nbsp;

///ORM框架对象

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 ) &gt; 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 ) &gt; 0 )

{

RefreshList( myWork );

}

}

}

}

}

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

部署快速ORM框架

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

小结

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

 

[转载]C# 之 类复制 MemberwiseClone与Clone(深 浅 Clone)

mikel阅读(970)

[转载]C# 之 类复制 MemberwiseClone与Clone(深 浅 Clone) – 老皆知 – 博客园.

MemberwiseClone 方法创建一个浅表副本,具体来说就是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。

为了实现深度复制,我们就必须遍历有相互引用的对象构成的图,并需要处理其中的循环引用结构。这无疑是十分复杂的。幸好借助.Net的序列化和反序 列化机制,可以十分简单的深度Clone一个对象。原理很简单,首先将对象序列化到内存流中,此时对象和对象引用的所用对象的状态都被保存到内存 中。.Net的序列化机制会自动处理循环引用的情况。然后将内存流中的状态信息反序列化到一个新的对象中。这样一个对象的深度复制就完成了。在原型设计模 式中CLONE技术非常关键。

下面的代码就是演示这个问题:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace CloneDemo
{
    [Serializable]
    class DemoClass
    {
        public int i = 0;
        public int[] iArr = { 1, 2, 3 };

        public DemoClass Clone1() //浅CLONE
        {
            return this.MemberwiseClone() as DemoClass;
        }

        public DemoClass Clone2() //深clone
        {
            MemoryStream stream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, this);
            stream.Position = 0;
            return formatter.Deserialize(stream) as DemoClass;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            DemoClass a = new DemoClass();
            a.i = 10;
            a.iArr = new int[] { 8, 9, 10 };
            DemoClass b = a.Clone1();
            DemoClass c = a.Clone2();

            // 更改 a 对象的iArr[0], 导致 b 对象的iArr[0] 也发生了变化 而 c不会变化
              a.iArr[0] = 88;

            Console.WriteLine("MemberwiseClone");
            Console.WriteLine(b.i);
            foreach (var item in b.iArr)
            {
                Console.WriteLine(item);
            }

            Console.WriteLine("Clone2");
            Console.WriteLine(c.i);
            foreach (var item in c.iArr)
            {
                Console.WriteLine(item);
            }

            Console.ReadLine();
        }
    }
}

[转载].NET日志工具介绍

mikel阅读(1307)

[转载].NET日志工具介绍 – 我为代码狂 – 博客园.

   最近项目需要一个日志工具来跟踪程序便于调试和测试,为此研究了一下.NET日志工具,本文介绍了一些主流的日志框架并进行了对比.发表出来与大家分享.

综述

所谓日志(这里指程序日志)就是用于记录程序执行过程中的信息,以便开发、测试和支持人员在调试或测试时利用这种技术来快速分析和定位程序中的问题和缺 陷。日志通常将程序执行中的信息以文本的形式写入日志文件或将程序数据发送到监视器(比如说数据库、远端计算机、邮箱、网页等)。高级一些的日志工具还支 持对一些复杂的数据结构、调用栈、线程等进行实时监控和记录。

大多数.NET的日志框架有着一些共同的概念和特性。根据作用来分主要有以下组件或概念:记录器(Logger)、监视器/目标(Monitor /Target)、包装器(Wrapper)、过滤器(Filter)、布局(Layout)、严重性级别(Severity)。

1记录器:日志的对象,可以同时连接一个或多个不同的监视器,记录各种不同的消息和信息。

2监视器:用于存储和显示日志消息的媒介,有多种存在形式。如一般的文本文件、数据库、网络、控制台、邮箱等。

3包装器:用于制定日志记录的方式。如:同步/异步记录,(出错时)回滚记录。

4过滤器:用于根据严重性级别来过滤和忽略某类消息,只记录特定级别的日志消息。

5布局:用于格式化输出。定义输出项和输出格式。

6严重性级别:是对消息的分类,严重性级别表示消息的严重程度。也是过滤器的过滤依据。如:调试(Debug)、消息(INFO)、警告(WARN)、错误(ERROR)、严重错误(FATAL)严重性依次增强。

与上述对应的是一般的日志框架会提供的一些基本特性和功能:

1记录简单的文本消息。

2消息级别的过滤。

3允许多个监视器。

4记录消息种类和会话。

5根据时间或大小将文件分割。

6通过配置文件来控制日志,与代码分离。

当然还会有些高级功能,如线程的安全以及同步或异步,数据结构或对象的记录,可视化的日志查看器和分析器等等。

现在,已经有很多成熟的.NET日志工具,免费的、收费的、开源的都有。 一些主流的日志工具和框架有:NLog、log4net、Enterprise Library、ObjectGuy Framework等。接下来主要介绍一下ObjectGuy Framework、log4net、NLog这三种日志框架。

 

ObjectGuy Framework

一种轻量级的.NET日志工具。能提供基本的日志特性并具有很好的扩展性。它是一种纯代码控制的日志工具,小巧便捷,语法简单直接,很容易上手和使用。

ObjectGuy Framework中一般的logger只能对应一种监视器,而且都有相对应的类,这些类都是继承自logger这个抽象基类。因此实例化一个 logger很简单,只要调用那个类即可。如TextWriterLogger、FileLogger、SerialSocketLogger分别对应监 视器为控制台、文件、网络的logger。当然如果你想绑定多个监视器,ObjectGuy提供了CompositeLogger这个类,并有相应的 AddLogger方法可以将上述logger子类加入到CompositeLogger中与其绑定,这样当CompositeLogger进行记录时, 其加入的普通logger都将同时自动记录。ObjectGuy还有一个InsistentLogger类,顾名思义它是一个帮助的logger。当其绑 定的主logger发生异常时,该InsistentLogger会自动启动尝试进行记录。

ObjectGuy 中的消息分为7种严重性级别:Debug, Info, Status, Warning, Error, Critical, Fatal。默认级别为Debug。每一种级别都有对应的消息记录方法LogXXX。消息过滤也很简单,只要设置一下logger的 SeverityThreshold的级别,那么所有级别低于SeverityThreshold的消息将被忽略。关于消息的格式化输 出,ObjectGuy提供了LogEntryFormatter类来制定相应的格式化。由于ObjectGuy中的所有特定logger都是继承自 Logger这一抽象类,因此构造自定义logger也很简单,只要继承该抽象类并重写相应的方法即可。下图为ObjectGuy中的类层次结构:

可以看出ObjectGuy是一个非常简单易用的日志工具,它可以满足基本的日志要求和特性而且有很好的扩展性。但是由于ObjectGuy非常简洁,一 些高级特性没能被提供。其实ObjectGuy好比是编辑工具中的记事本,简便、快捷。如果只是使用一些基本特性而对word又不是死磕的 话,ObjectGuy还是一个非常不错的选择。(PS:ObjectGuy是一个付费工具,非商业版只要5美元。主页:http://www.theobjectguy.com/dotnetlog/

 

log4net

log4net是Apache上的一个开源项目,是基于非常成功的log4j(log for java)架构完成的。其实是log4j的.NET版本,但又结合了.NET的特性。log4net提供了很多功能,除了一些基本特性外,log4net 支持配置文件控制。程序运行时只要修改配置文件就能对日志进行控制,实现和代码的分离。另外log4net支持很多.NET框架,从1.0到4.0中的各 种版本,同时也支持Mono(跨平台)。log4net中的监视器(Target)要比ObjectGuy丰富得多,有20种之多。

log4net中logger的使用不像ObjectGuy那样直接。创建一个logger实例需要通过LogManager的GetLogger 方法来完成。由于log4net中的logger是和类对应的(类级别日志,class-level logging),因此需要类名或类的类型typeof(类名)作为GetLogger的参数来获取该类的logger的一个引用。相同的类名(指完整类 层次名:namespace.className)指向同一个logger。在log4net中很特别的一点是logger的层次关系,一个logger 可以有祖先logger和子logger,他们之间存在一些继承关系。主要有两方面:

1严重性级别的继承:log4net中有All、Debug、Info、Warn、Error、Fatal、Off七种级别,严重程度和优先级依次上升。若当前logger的级别为空(未赋值),则其继承父logger的严重性级别。

2监视器(log4net中称Appender)的继承:也叫监视器的可加性。当前logger的监视器如果为空,则继承祖先logger的监视 器。若不为空,则当前logger的监视器会自动添加祖先logger的监视器。也即祖先监视器会继承给子监视器,这种特性依赖Additivity的 值。其默认为真。可以看出与ObjectGuy不同的是,log4net中的logger可以任意绑定多个监视器,只要定义相应的Appender然后添 加即可。

log4net中存在一个根logger(root logger),它是所有logger的祖先,严重性级别默认为Debug。其他的logger则会依据完整类层次名称来区分祖先和后代关系。例如:对应 于X.Y的logger的父亲为对应于X的logger。当然只有这些logger被实例化之后这种关系才会生效。log4net中还提供对象呈现器 (Object Renderers),它可以提供一种用户自定义的对象呈现规则。当该对象被记录时,对应的对象呈现器会自动触发。

log4net的另一个特色是它的配置文件,配置文件可以像代码一样对logger进行各种设置。而且可以在程序运行时进行方便的进行修改从而得到需要的 日志记录。配置文件是XML格式,因此有很好的标记性,很容易书写。配置文件的标签包含大量API中logger相关的类。如:root(根 logger)、logger、appender、layout、renderer、filter等。完全能够像代码一样对程序中的日志进行设置和管理。

综上所述,log4net能够提供非常多的特性支持,相比ObjectGuy而言有过之无不及。而且有很多第三方的可视化日志查看器和分析器支持 log4net使其更加直观。然而log4net的架构也很复杂,因此使用起来没有ObjectGuy那样简单直观。另外由于log4net不能提供异步 记录,如果插入的代码很多,势必会影响应用程序的性能。(PS:log4net的更新非常缓慢,维护人员不多。从1.2.10版本到1.2.11版本花了 4年时间(2006-2011)。主页:http://logging.apache.org/log4net)

 

NLog

NLog和log4net非常相似,都是开源工程,都支持.NET框架和跨平台,都有配置文件,都有丰富的监视器等等。可以说他们提供的特性绝大部分都是相同的。不过差别还是有的,下面主要讲一下两者的不同之处。

和log4net一样,NLog中的logger也是类级别的。实例化一个logger除了像log4net一样用 LogManager.GetLogger(“ClassName”)也可以直接用 LogManager.GetCurrentClassLogger()获取当前类的logger。与log4net不同,NLog中的logger没有 层次结构,因此也不存在继承等特征,所以NLog中logger的严重性级别和监视器(target)必须要定义。这一点跟ObjectGuy较像,简单 直观。严重性级别上,NLog有Debug、Trace、Info、Warn、Error、Fatal六种。除了监视器,NLog中多了一层包装器 (Wrapper),顾名思义它是用来包装监视器的。NLog提供了很多包装器。如AsyncWrapper:异步包装器,使logger可以异步地将记 录写入包装器里面的监视器,从而提高应用程序的响应速度,保证应用程序的性能不受太大影响;FallbackGroup:回滚组,当logger出现错误 时回滚。由此可见NLog是支持异步和回滚的,这使得NLog的性能比log4net要好。至于配置文件方面,两者只是标签和属性不同而已。

其实NLog更像是ObjectGuy和log4net的结合,既有ObjectGuy的简单易用,又有log4net的绝大部分强大特性。而且NLog 支持异步记录,使得NLog的整体性能要优于log4net。NLog也有一些第三方图形查看器和分析器。同时NLog更新频繁,有很好的维护,应该是一 个很不错的选择(个人推荐)。由于NLog使用起来非常方便(无论是通过代码还是配置文件),这里不做过多介绍,详细信息参看NLog主页:http://nlog-project.org/

 

最后附上一些主流.NET日志工具的综合性能比较:

(PS:SmartInspect为纯商业.NET日志工具,Enterprise Library为微软推出的日志库。)

[转载]如何统计代码行执行的时间?

mikel阅读(1261)

[转载]如何统计代码行执行的时间? – b0b0 – 博客园.

1.         问题

请计算下列代码执行所耗费的时间,给出通用的解决方法?

List myList=new List();

for(Int32 i=0;i&lt;ICount;i++)

{

myList.Add(i);

}

2. 解决方法

using (OperationTimer op=new OperationTimer ("Generic "))

{

ListmyList=new List();

for(Int32 i=0;i&lt;ICount;i++)

{

myList.Add(i);

}

}

}

OperationTimer 类会在调用构造函数时取得时间t1,在调用Dispose()取得时间t2,中间过程耗费的时间=t2-t1. Using 语法会在using 块的末尾调用using中声明对象的Dispose方法。
3. 代码

using System;

using System.Diagnostics;

using System.Collections;

using System.Collections.Generic;

namespace genericApp

{

///计时辅助类

public class OperationTimer:IDisposable{

private Int64 m_startTime;

private string m_text;

private Int32 m_CollectionCount;

public OperationTimer(string text)

{

m_startTime=Stopwatch.GetTimestamp();

m_text=text;

}

public void Dispose()

{

string text=string.Format("Time={0,6:###.00}",(Stopwatch.GetTimestamp()-m_startTime)/Stopwatch.Frequency);

Console.WriteLine(string.Format("{0}:{1}",m_text,text));

}

}

class MainClass

{

public static void Main(string[] args)

{

ValueTypePerfermanceTest();

Console.Read();

}

static void ValueTypePerfermanceTest()

{

const Int32 ICount=20000000;

using (OperationTimer op=new OperationTimer ("Generic "))

{

ListmyList=new List();

for(Int32 i=0;i&lt;ICount;i++)

{

myList.Add(i);

}

}

using (OperationTimer op2=new OperationTimer ("ArrayList "))

{

ArrayList myList=new ArrayList();

for(Int32 i=0;i&lt;ICount;i++)

{

myList.Add(i);

}

}

}

}

}

4.         Mono 2.4 中的运行结果

 

Generic.jpg

[转载]网站性能指南(一)概述

mikel阅读(1224)

[转载]【网站性能指南】(一)概述 – 技术弟弟 – 博客园.

【概观】

  • 什么使我们的网站变慢?
  • Http 协议
  • 通过更好的性能省钱和赚钱
  • 性能规则

【什么使网站变慢】

  • 对于大多数现代的网站,仅仅有10%-20%的响应时间是用于生成和加载html document的。

那么,其他的时间用于加载什么呢?确切的说,如下:

  1. css
  2. JavaScriptJQuery,plugin 等等)
  3. images

让我们使用一个工具去看看-Fiddler (http://www.fiddler2.com/fiddler2/)

首先打开fiddler,然后使用浏览器访问 www.microsoft.com

这时候我们可以看到fiddler监控到客户端与微软网站服务器之间的交互情况如下图:

 

注意fiddler 右边的选项卡,有一个timeline。载入default.asp话费了大概1s的时间。让我们全选这些行。再去看看timeline,如下图

 

这时候我们根据数据就可以证明前边的观点,对于某个网站来说,80%的时间是用于加载css/JS/image的。

 

【Http 协议】

对于http协议的理解是非常重要的,因为它定义了web 浏览器和 web 服务器如何交互。

  • Hypertext Transfer Protocol

对于这点来说,最重要的是text,它不是基于二进制的协议,而是基于text的。

  • 协议是1997年1月在 RFC(Http1.1)定义的。
  • 请求/相应 模式。客户端浏览器发送特定的request,服务器返回response
  • Header 和body。 每个request/response 有header和body

以下是 http协议的内容。我十分推荐你们阅读它:http://tinyurl.com/8395lq

 

HttpRequest

我们使用fiddler观察一下我们访问微软的httprequest。

选中第一个记录。右边选项卡依次->Inspecotrs->Raw

我们简单分析,

Get指明了url和http的版本。

host指明了host的地址。

accept-language指明了浏览器使用的语言

accept-Encoding指明了是否可以使用对浏览器到服务器之间的数据进行压缩。

 

HttpResponse

下面我们来看看刚才我们请求的响应。

我在fiddler中选中的是第12行数据,选中右边选项卡->Inspecotrs->Raw

# Result Protocol Host URL Body Caching Content-Type Process Comments Custom
12 200 HTTP www.microsoft.com /en-us/homepage/shared/core/2/js/js.ashx?& 3 private text/JavaScript; charset=utf-8 chrome:3856

 

Http/1/1 200 OK。是告诉大家,一切运行良好。 200是一种状态,如果遇到问题可能会是404,500等。
其他细节,大家可以自己查一下资料。

【通过更好的性能省钱和赚钱】

大家都可以明白。提升网站的性能,可以让用户更加满意。而这也能让我们省钱和赚钱。

  • 省钱的办法:

使用更小的带宽

更少的服务器数量

  • 赚钱的办法:

增长的销售和流量

    • -每增加100毫秒载入Amzon.com 会减少销售额的1%.web
    • -当google maps 的主页大小从100KB减少到70-80k时,流量在第一周会增在10%,在随后的3周会增长到25%。
    • -google 已经根据你网站的性能去帮助决定搜索排名。

对于网站性能对流量和销售产生的影响请参见相关文章《web性能心理学》http://www.websiteoptimization.com/speed/tweak/psychology-web-performance/

 

【性能规则】

  1. 减少http request

如何减少http request呢?我们根据上边提到fiddler侦听到的http request 得知,好多次请求是去获取css,JavaScript,和image的。

首先我们先来看看一个网站:

它是一个普通的网站他可以使用JQuery来弹出图片,我们用fiddler 来试试它。

我们可以看出。他包含了一些css文件,也使用了jQuery

让我们看看这个网站的另一个版本。

样子是一模一样的,我就不show图了。

让我们看看fiddler 又帮我们抓到了什么:

js和css文件都变成1个了。我们把上边的js文件合并成1个js文件。这样我们就减少了httprequest的次数。

2.发送尽可能少的数据

我们回到fiddler。查看第一个网站的jQuery文件“jQuery-1.6.2.js。

它的普通版本是236k。

第一个网站需要加载js的总大小是279k。

而第二个网站需要加载js的大小是50.8k。

我们做了什么呢?只是把js文件里的白空格去掉了,就是对js文件的压缩。

css文件也如此。在最后的product版本上,我们使用合并的文件可以减少httprequest次数。当然在Debug的时候我们要保留空行增加代码的可读性。

关于压缩js的工具我们在网上可以找到很多,就不列举。

3.减少交互的次数(适当使用缓存)

让我们刷新一下第二个网站,并观看fiddler。我们可以发现,第二次加载至向服务器获取了default.aspx。

并没有重新加载js、css和图片。因为浏览器已经替我们缓存了那些文件。

 

后续的章节是

【性能测量】

【基础实施性能】

【前段性能】

【五花八门的性能】

敬请关注