[转载]InfoQ: MonoTouch:用.NET开发iPhone应用

mikel阅读(1108)

[转载]InfoQ: MonoTouch:用.NET开发iPhone应用.

引言

直到最近,要为苹果的iPhone开发应用程序的唯一选择就是一头扎进苹果的开发系统中。这意味着,你必须“愿意”在XCode IDE中编写Objective-C代码。对于很多开发人员,学习Objective-C被看作是一个巨大的障碍。特别对于哪些从来不用担心内存管理、指 针和C语言要负责处理的东西的开发人员来说,更是如此。

随着MonoTouch框架(Novell的Mono Project的一部分)的出现,这一切都将改变。Mono Project是微软.NET平台的开源实现。其允许你在几乎任何平台上运行.NET应用程序,包括Apple、FreeBSD、Linux、Unix等 等。MonoTouch是Mono Project的新组成部分,让你能够用C#和.NET平台编写可以运行在iPhone上的应用程序。

本篇文章的目的,就是提供MonoTouch平台的一个完整介绍,让大家知道到那里获取必要的工具,MonoTouch有什么样的限制,以及如何构 建一个简单的应用程序。

背景和限制

对于打算为iPhone开发应用的.NET开发人员而言,MonoTouch的出现无疑是一件好事。然而,在决定创建应用程序之前,有一些限制和背 景知识需要先了解清楚。

它如何工作?

在创建MonoTouch应用程序的时候,大部分非UI方面的.NET 3.5功能依旧可用,或者一些还处于计划之中(如.NET 4.0的功能)也囊括其中。这让你可以使用很多业已熟悉的.NET Framework技术来编写应用程序,包括Windows Communication Framework (WCF)、Workflow Foundation (WF)等等。也包括几乎所有的基类库(Base Class Library,BCL),涵盖诸如垃圾收集(Garbage Collection)、线程、数学函数、System.Net、加密等。对于可用的标准.NET程序集列表,见http://monotouch.net/Documentation/Assemblies。 MonoTouch是一种基础.NET函数库的特别定制版本,这也类似Silverlight和Moonlight的实现方式。

这意味着,你能够使用MonoTouch的核心程序集来编译标准的.NET 3.5代码成相应的函数库,并在你的应用程序中使用它们。因此,如果你有一个用于其它应用程序的特别函数库,其包含着一些用于工程问题的高级数学函数,那 么只需简单地把这些代码库加入到你的MonoTouch解决方案中,并引用它。在你构建解决方案的时候,编译器就利用MonoTouch核心函数库对其进 行编译,接着就能在iPhone应用程序中使用它了。

MonoTouch也包括一些原生iPhone API的包装函数库,如访问位置(Location,GPS)、加速计、地址簿等的函数。MonoTouch也提供相应的功能,让你能够调用那些尚未进行 包装的原生Objective-C函数库,所以你可以直接和现存的Objective-C代码进行互操作。

我如何创建用户界面(UI),我能使用Silverlight吗?

MonoTouch应用程序的UI需要使用苹果的Interface Builder(界面创建器,IB)应用程序来创建,IB连同iPhone SDK一起提供。Interface Builder使用Cocoa Touch(苹果用于iPhone的UI框架)控件对象,这些控件对象在iPhone上原生提供的。这意味着,你能在应用程序中使用所有的标准 iPhone控件,如选择器(Pickers)、滑动条(Sliders)、按钮等等。

你也能通过代码来创建界面,即实例化Cocoa Touch对象后,把它们添加到应用程序的视图(Views)中(关于视图,后面会详细讲述)。

然而,你不能利用传统的.NET技术,如Silverlight、WPF、WinForms等来创建MonoTouch界面。

Cocoa Touch使用一种融合了MVC(Model View Controller)模式思想的结构,我们将在后面一篇文章中介绍。

我如何分发我的应用?

MonoTouch应用程序的分发完全和传统iPhone应用程序的分发一样,既可以通过苹果App Store,也可以通过企业部署。

App Store是一个在线资源库,让用户可以付费购买(如果不是免费的话)和下载应用程序。可以从iTunes中访问,或直接通过iPhone本身来访问。为 了得到通过App Store分发应用的许可,你必须向苹果注册,并支付每年99美元的年费。

企业部署方式就是为公司开发内部应用程序,并分发给员工等人员使用,无需把应用在App Store中列出。

什么是许可模型?

不像Mono那样,MonoTouch不是开源的,且是一个收费产品。这意味着,如果你打算开发一些实际的应用,就必须购买软件许可。

  • 专业版($399)——单个的个人开发人员许可,让你可以开发应用程序,并通过苹果App Store来分发它们。
  • 企业版($999)——单个的企业开发人员许可,让你可以开发应用程序,并通过苹果App Store来分发它们,或者进行企业部署。
  • 企业版,5人($3999)——和企业版一样,只是提供了5个坐席的授权。

以上所有选项都包括了一年的免费升级权益。

还有一个评估版本,你只能把应用部署到模拟器中。出于介绍的目的,我们只需要评估版本就行。

MonoTouch有哪些限制?

没有即时(JIT)编译

根据苹果的iPhone政策,任何应用程序都不能包含需要JIT编译的代码。但是稍等,.NET确实能正确工作,是不?对,不过MonoTouch 是通过把应用程序编译为原生的iPhone程序集来跳过这个限制的。但是,这也带来了几个限制。

  • 泛型——泛型是由JIT编译器在运行时进行实例化的,然而,Mono具备一种提前(Ahead of Time ,AOT)编译的模式,可以为类似List<T>这样的泛型集合生成方法和属性。而泛型的其他用法,例如泛型虚方法、泛型类型上的 P/Invokes和Dictionary<TKey, TValue>上的值类型,就不被支持(虽然存在Dictionary<TKey, TValue>的代替方法)。
  • 动态代码生成——因为动态代码生成依赖于JIT编译器,所以对任何动态语言编译的过程也不能支持。包 括System.Reflection.Emit、 Remoting和动态语言运行时(DLR)。

C#是唯一的语言

另外,目前用于编写MonoTouch应用程序的唯一可用语言是C#。Visual Basic.NET有望在MonoTouch未来的发布中支持,不过此时此刻我们别无选择。

更多信息

相关限制的完整列表和更多的信息,包括一些代替方法,可用参见http://monotouch.net/Documentation/Limitations

入门

要进入为iPhone创建MonoTouch应用程序的大门,我们需要下面几样东西:

  • 一个使用Intel CPU的Mac电脑,其要安装MacOSX 10.5或10.6 (Leopard或Snow Leopard)
  • 苹果的iPhone SDK 3.0或更高版本
  • Mono的当前版本
  • MonoTouch SDK
  • 一个IDE工具,如MonoDevelop或XCode,或一个文本编辑器程序

安装着Leopard或Snow Leopard的Mac

这是最重要也是最容易忽视的需求。尽管,理论上你能在任何平台上开发大部分应用程序,然而iPhone Simulator和Interface Builder只能在Leopard和Snow Leopard上运行。另外,编译器本身用到了一些特定于Intel Mac机器的底层功能,所以购买这样一台电脑是绝对必须的。

苹果的iPhone SDK

iPhone SDK可通过http://developer.apple.com/iphone/来 免费下载,不过必须在苹果网站上注册,才能访问这个地址。

在安装了iPhone SDK后,要确保你能正常启动iPhone Simulator。要启动它,只需打开Spotlight,键入iPhone Simulator。

Mono

一旦你测试iPhone Simulator正常,那么就要安装Mono for OSX的最新版。Mono可以从http://mono-project.com/Downloads下 载。记住要点击“Intel”版本的链接,不要点CSDK版本。同样,安装MonoTouch SDK之前也需要安装Mono的。Mono的安装包是磁盘镜像的形式,挂接镜像,双击安装包,根据安装向导完成安装过程。

MonoTouch SDK

接下来,下载和安装最新的MonoTouch SDK。你既可以在MonoTouch商店(http://monotouch.net/Store)购买,购买后会 收到一个下载链接,也可以从http://monotouch.net/DownloadTrial下 载评估版。如果购买了MonoTouch,你就能把应用程序部署到一台正确配置了的iPhone上,不过也可像我这样,仅仅在模拟器中运行。所以,目前 而言试用/评估版就足够了。

文本编辑器或集成开发环境(IDE)

如果你打算创建MonoTouch应用程序,所需的所有东西就是前面提及的,和一个文本编辑器。你能创建所有代码文件,并用命令行(终端窗口)来手 动编 译。这种方式虽然可行,但是实际操作起来可能会非常痛苦,所以我们还是需要使用一个IDE来开发我们的应用程序。

你可以编辑/hack一下XCode(随iPhone SDK一起安装)来利用MonoTouch的函数库和编译器,也可以使用MonoTouch版的MonoDevelop,其已经为MonoTouch应用 程序做好所有配置了。我们理所当然要用MonoDevelop,所以访问这里http://monodevelop.com/Download/Mac_MonoTouch来 下载它。要安装MonoDevelop,只用把下载文件拖到应用程序目录中就行。

如果你已经正确安装Mono,那么MonoDevelop应该可以正常启动。

Hello World应用程序

现在,一切已经准备妥当,让我们开始来尝试开发点东西了。

MonoDevelop

首先,启动MonoDevelop。你应该会看到和下图类似的界面【译者注:如果OSX的首选语言是中文的话,MonoDevelop的菜单和工具 栏的文字显示不正常,所以最好把English拖到语言选项的第一位】:

作为一个标准的IDE,看上去还是蛮熟悉的。它非常类似Visual Studio、SharpDevelop、Visual C# Express等等。

我们创建一个新解决方案,来包含iPhone项目。这里的解决方案和Visual Studio中的概念一样,实际上你可以在MonoDevelop中打开Visual Studio创建的解决方案。在MonoDevelop中的一个不同点就是,你能够在一个MonoDevelop实例中打开多个解决方案,正如下面的截图 所示:

这完全是由于在OSX中,你不能启动MonoDevelop的多个实例(事实上,任何程序都不行),且没有任何变通方法。所以,如果你需要在解决方 案间切 换(例如,你希望另外打开一个包含示例代码的解决方案),你就能简单地一次性打开多个。

那么,说了上面这么多,让我们现在来创建一个解决方案吧。在菜单中,点File:New:Solution:

我们要创建一个如下面截图所示的“iPhone MonoTouch Project”。选中它,并命名为Example_HelloWorld_1。

这里再次和Visual Studio中创建新解决方案的对话框很类似。点击Forward ,显示下一屏,直接点击OK,因为我们不需要这些功能:

你现在应该可以看到如下所示的解决方案视图了(注意,我展开了解决方案中的节点,以便显示出所有文件和引用程序集):

我们来过一遍这些东西:

  • References ——这个文件夹包含MonoTouch应用程序需要的基本引用。MonoTouch程序集包括特定于iPhone的任何东西,也包括了所有Cocoa Touch控件的包装器,以及类似位置、数据等核心iPhone接口。以System.开头的程序集是.NET的基类库和运行时,其被裁减过以便能运行在 iPhone上。
  • Main.cs ——这和控制台应用程序、WPF应用程序等是一致的。在这里调用的是static void main() ,其是作为应用程序的入口点。过一下我们会仔细研究一下这个文件。
  • MainWindow.xib和MainWindow.xib.designer.cs ——这个和Winforms Window或WPF Window类似。xib文件实际上要在Interface Builder中进行编辑,而designer.cs文件包含这个窗体的属性。

让我们来仔细研究一下Main.cs文件中的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Example_HelloWorld_1
{
	public class Application
	{
		static void Main (string[] args)
		{
			UIApplication.Main (args);
		}
	}

	// The name AppDelegate is referenced in the MainWindow.xib file.

	public partial class AppDelegate : UIApplicationDelegate
	{
		// This method is invoked when the application has loaded its UI and its ready to run
		public override bool FinishedLaunching (UIApplication app, NSDictionary options)
		{
			// If you have defined a view, add it here:
			// window.AddSubview (navigationController.View);			

			window.MakeKeyAndVisible ();			

			return true;
		}

		// This method is required in iPhoneOS 3.0
		public override void OnActivated (UIApplication application)
		{
		}
	}
}

其中有两个有趣的地方。它包含一个Application类和一个AppDelegate类。从这里开始,和传统的.NET GUI开发就有所不同了。

iPhone应用程序的工作方式是,应用程序类(继承于UIApplication)包含了所有窗口、视图、控件和资源等等;同时应用程序委托类中 (继承于UIApplicationDelegate)处理来自iPhone OS的回调,实际上会包括应用程序运行周期(Lifecycle)的事件(例如应用程序启动和应用程序终止)和大量的运行时事件(例如低内存报警)。

通过在应用程序委托类处理这些事件,你就能够响应它们。比如,在应用程序关闭的时候,WillTerminate()方 法将被调用,这样就有机会去保存任何用户数据、应用程序状态等。

在Application类中,具有一个Main() 方法。调用UIApplication.Main,Objective-C运行时将查找MainWindow.xib文件(它包含了 UIApplicationDelegate类的名称),实例化Application类(为单实例)后,就接着调用AppDelegate类中运行周期 事件。

你不必把主窗体的名称命名为“MainWindow.xib” (即所谓的主界面文件)。你可以任意命名它,只需告知编译系统去查找哪个文件就行。如果你希望它去查找不同的文件,在项目文件上右键打开项目选项,点击 Options ,接着在Build : iPhone Application : Main Interface File中即可设置主窗体对应的文件。在应用程序启动的时候,Objective-C运行时将会尝试去加载那个文件,并根据这个文件内的设置来查找应用程 序委托类。

另外,你也可以任意命名你的应用程序委托类。默认情况下,它的名称为“AppDelegate”。要改变它,在Interface Builder中打开主界面文件,修改Application Delegate的名称。

一会我们会回到Main.cs文件上来,不过首先来深入研究下应用程序的实际GUI。

Interface Builder

目前为止,已经读到了我们的iPhone应用程序的部分代码,可以深入研究一下如何构建它的界面了。苹果的应用程序设计工具包称之为 Interface Builder。Interface Builder可以和开发环境松耦合。它可以编辑那些定义应用程序GUI的.xib文件。NIB和XIB的比较:Nib文件包含了窗口、控件等的XML表 示,类似于WPF/Silverlight中的XAML模型。

不管是在XCode中编写Objective-C,或在MonoDevelop中编写C#,Interface Builder的用法都是完全一样的。能这样,完全是因为MonoDevelop能够监测Nib文件的变更,并添加/删除对应于Nib文件的适当代码到 designer.cs文件中。

你当然可以不打开Interface Builder,纯粹通过编程来创建整个GUI,有些开发人员确实也是这样做的。很多事情在Interface Builder也无法完成,就此而言,你还是需要编程来完成某些事情。Interface Builder隐藏了一些复杂的部分,在入门的时候,很容易使用Interface Builder来熟悉iPhone应用程序GUI的一些概念。

那么,说了这么多,让我们来着手创建界面了。在MainWindow.xib文件上双击。Interface Builder将启动,并能看到如下图所示的东西:

让我们逐一研究一下这些窗口。从左到右,分别是:Document Window(文档窗口)、Design Surface Window(设计界面窗口)、Library Window(控件库窗口)和Inspector Window(检查器窗口)。

首先让我们来看Document Window:

这个窗口显示了在.xib文件中的所有对象。这是默认的视图,你会发现它虽然样子漂亮,但没有太大用处,因为你的对象实际上是层级排列的,而图标视 图只能同时显示一级。在我们添加控件到界面上的时候,它不会显示在这个视图中。所以,我们需要通过点击View Mode工具条上中间的图标来改变列表视图。改变后,应该会显示如下的样子:

下一个窗口是设计界面。也就是,我们实际拖拽Cocoa Touch控件来设计我们界面的地方:

当然,由于我们尚未添加任何控件到上面,所以现在还是一片空白。

下一个窗口是Library。Library包含了所有能在设计界面上使用的Cocoa Touch控件。如果你运行Leopard【译者注:原文这里为Snow Leopard,实际是错误的。】,那么你的控件库窗口如下所示:

如果你运行Snow Leopard【译者注:原文这里未Leopard,实际是错误的。】,它应该是这个样子:

注意它们两者还是非常类似的。在Snow Leopard里,有一个名为“Classes”的标签页,我们后面会讲到这个东西,不过这也是唯一的不同点。

这是Library的默认视图,不过我喜欢稍微不同的样子,以便我能一下看到更多的控件。为了改变Library窗口中的视图,右键点击控件视图, 可以选择不同的显示风格。你也可以点击窗口左下角的Gear(齿轮)按钮。下图是Leopard中的“icons and labels”风格:

而在Snow Leopard中是:

最后一个窗口是Inspector Window:

Inspector具有四种不同的视图,可以通过窗口顶部的标签栏来选择。这些视图分别是Attribute Inspector、Connections Inspector、Size Inspector和Identity Inspector。Inspector大致和Visual Studio中的Property Explorer类似。它向你显示当前选中的Cocoa Touch对象的所有属性。也可以用它来设置可视化属性、布局等等。在创建Outlets和Actions时也用得着它,这个主题后面会谈到。在下面的图 片中,我们在Document窗口中选择了Window对象,所以我们可以查看这个对象的相关属性。

现在,我们对Interface Builder窗口已经大致浏览了一遍,接下来让我们实际地创建一些东西。我们要创建如下这样的界面:

首先,拖一个“Round Rect Button(圆角矩形按钮)”(UIButton)到窗口上。接着,在按钮上双击来设置文本。在这个过程中你会注意到,你会获得少许的指导。这些指导是 基于苹果的人机交互向导(Human Interface Guidelines)的,来辅助你在视图上以适合的间距等来定位控件。

在窗口上添加了按钮后,拖入一个“Label(标签)”(UILabel)控件。改变它的尺寸以便接近窗口的宽度。接着双击这个label控件,删 除文本以使应用程序启动的时候,标签的内容是空白的。

如果你正确地完成了所有步骤,那么你的Document Window将显示如下的样子(点击“Window”旁边的箭头就会看到它包含的子控件):

我们现在创建了第一个窗口界面。不过,不像传统的.NET GUI开发,你还不能编程访问这些控件。假如这是一个WPF应用程序,你一拖动控件到设计界面上,你就能通过this.ControlName这样的形式 来访问它。如果你马上去查看MainWindow.designer.cs文件,除了“window”属性外,你看不到任何其他代码。为了让这些对象能被 代码访问,我们必须通过Outlets把它们关联上。你在Interface Builder中创建一个Outlet(对象关联口)的时候,MonoDevelop将在这个类的designer.cs文件中添加一个匹配的属性,让你 可以编程访问这些控件。

Outlets(对象关联口)

我们来为之前添加的标签和按钮控件添加outlets,以便能在代码中访问它们。这点在Leopard和Snow Leopard上也有所区别,所以大家要严格遵循这里提到的正确指示。

【SNOW LEOPARD指示开始】

确保Library Window是打开的。点击顶部的“Classes”标签。点击第一个下拉列表框,这个就是所谓的“Library”,滚动到最后选择“Other Classes”。就会显示在你的项目中存在的自定义类。从上面列表中选择AppDelegate,接着选择下面的“Outlets”标签:

点击“+”按钮两次来创建两个新的Outlet。

【SNOW LEOPARD指示结束】

【LEOPARD指示开始】

首先,保证在Document Window中App Delegate是被选中的。如果App Delegate未被选中,那么在创建Outlets的时候,它们的属性不会被创建,或者会在错误的地方创建。

接下来,来看Identity Inspector。Identity Inspector是Inspector窗口的最后一个标签页。现在,在Identity Inspector中定位到“Class Outlets”部分。在Class Outlets上,点击“+”按钮两次来创建两个新的Outlet。

【LEOPARD指示结束】

每个outlet都具有一个名称和一个类型。名称代表了控件属性的名字,这个类似于ASP.NET的ID,或WPF中的Name。类型是 Outlet的实际类型,比如UIButton、UILabel、UITextView等等。为了命名它们,在它们的名称上双击,键入相应的名称。对于我 们之前添加的outlet来说,我们修改为“btnClickMe”和“lblResult”。

目前,它们两者的类型都是“id”。如果你不改变类型,就能把它们挂接到任何东西上,因为id就意味着动态类型,本质上就是.NET世界中的 “object”。id类型虽然好,不过我们打算把它们改为实际的类型。过一会我们会看到这样做会有什么不同。现在,双击btnClickMe的类型,键 入“UIButton”。你的Class Outlets 窗口应该显示如下这个样子:

如果在里面没有window的outlet,意味着你没有在App Delegate创建outlet。如果这样的话,删除Outlets,保证在Document Window中选中App Delegate,重新创建outlets。

现在我们已经创建了这些outlets了,就需要实际地把它们关联到我们的控件上。首先,在Inspector窗口上点击第二个标签页,来选中 Connections Inspector。在Outlets部分,应该可以看到我们之前创建的两个Outlets了。然而,你不能把它挂接到任何东西上。注 意,“window”这个outlet已经挂接到“Window”对象上了。

为了挂接我们的Outlets,我们要从“Outlets”中的outlet圆点上,拖动到我们想要挂接的控件上。在这样做的时候,我们将会看到如 下所示的效果:

对两个Outlet都进行这样处理。你也可以从Connections Inspector拖到Document Window上。如果控件相互重叠的情况下,这样就很有用。下面的截图就描述了这种方式:

在我们这样做的时候,你可能会注意到一些有趣的事情。因为设置lblResult的类型为UILabel,所以在我们把它的 Outlet拖到Window的时候,它只允许我们挂接到那些类型一致的控件上,在这个例子中就是UILabel。另外一方面,btnClickMe能被 拖到任何东西上,因为它具有动态的ID类型。这就是我们要设置强类型outlet的一个原因,以便降低它挂接到错误控件上的可能性。当然,这不是必须的, 不过这样做是一个良好的习惯。

好的,现在我们创建好了界面了,outlets也挂接好了,让我们回到MonoDevelop把一起串在一起。

回到MonoDevelop

如果你打开MainWindow.designer.cs,在其中会到两个属性:

[MonoTouch.Foundation.Connect("btnClickMe")]

private MonoTouch.UIKit.UIButton btnClickMe {
	get {
		return ((MonoTouch.UIKit.UIButton)(this.GetNativeField("btnClickMe")));
	}
	set {
		this.SetNativeField("btnClickMe", value);
	}
}

[MonoTouch.Foundation.Connect("lblResult")]

private MonoTouch.UIKit.UILabel lblResult {
	get {
		return ((MonoTouch.UIKit.UILabel)(this.GetNativeField("lblResult")));
	}

	set {
		this.SetNativeField("lblResult", value);
	}
}

有了这两个属性,就可以让我们通过代码来访问标签和按钮了。在这里要注意一件有趣的事情——就算我们声明btnClickMe的类型为 id,而自动创建出来的属性也是强类型的UIButton。这是因为MonoDevelop足够智能,可以查看outlet背后实际的类型,以便创建适合 类型的属性。这对于我们很有用,因为意味着我们在每次使用它的时候,都无需把btnClickMe属性转换为UIButton类型。

现在回到Main.cs文件,查看AppDelegate。我们来看一下FinishedLaunching方法。

// This method is invoked when the application has loaded its UI and its ready to run

public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
	// If you have defined a view, add it here:
	// window.AddSubview (navigationController.View);
	
	window.MakeKeyAndVisible ();

	return true;
}

正如注释所建议的,这个方法在Application实例化后且已经准备好运行之时,由Objective-C运行时来调用。第一句 (window.AddSubview)被注释掉了,我们会在谈论Model View Controller(MVC)模式的时候来研究它的真正作用。

下一句window.MakeKeyAndVisible,设置MainWindow为主窗口并让其显示出来。在 iPhone开发中,真正有意思的事情是你永远有且有一个窗口在显示。如果想在iPhone应用程序中显示不同的界面,你要创建新的视图,并用视图控制器 把其“推”到前端。然而,你不调用这个方法,iPhone OS就不会发送事件到你的窗口上。MakeKey这个部分是真正起作用的,而AndVisible部分实际上留有传统OS X Cocoa框架的痕迹。

我们来添加一些新代码到这个文件中。在我们创建Outlets的时候,我们是在AppDelegate中创建它们的。那意味着它们会出现在 AppDelegate类中,所以可以在这里来访问它们。和传统的.NET GUI编程有一点不同的是,通常会有一个MainWindow.cs文件,在那里面来处理所有窗口相关的代码。而在这里,我们遵循Objective-C 的模式,就把代码放在AppDelegate中。

我们来把AppDelegate类改为如下这样子:

// The name AppDelegate is referenced in the MainWindow.xib file.

public partial class AppDelegate : UIApplicationDelegate

{
	//---- number of times we've clicked
	protected int _numberOfClicks;

	// This method is invoked when the application has loaded its UI and its ready to run
	public override bool FinishedLaunching (UIApplication app, NSDictionary options)
	{
		// If you have defined a view, add it here:
		// window.AddSubview (navigationController.View);

		window.MakeKeyAndVisible ();
		
		//---- wire up our event handler
		this.btnClickMe.TouchDown += BtnClickMeTouchDown;

		return true;
	}

	protected void BtnClickMeTouchDown (object sender, EventArgs e)
	{
		//---- increment our counter
		this._numberOfClicks++;
		//---- update our label
		this.lblResult.Text = "Hello World, [" + this._numberOfClicks.ToString () + "] times";
	}

	// This method is required in iPhoneOS 3.0
	public override void OnActivated (UIApplication application)
	{
	}
}

第一件事情,我们添加了一个变量来跟踪点击次数, _numberOfClicks。接着,我们添加这行代码:this.btnClickMe.TouchDown += BtnClickMeTouchDown;

这就把btnClickMe的TouchDown事件(类似于OnClick)挂接到处理程序BtnClickMeTouchDown上

接着,在BtnClickMeTouchDown中,我们简单地用按钮被点击多少次的数量值来更新标签的显示内容。

好了,我们已经完成了所有编程,让我们来构建和运行一下。首先来构建。在菜单中,选择Build : Build All。如果目前为止你都正确的按部就班的话,它应该能构建成功。下一步,就是在iPhone模拟器上运行它。在工具栏上,确保 Debug|iPhoneSimulator被选中,如图:

接着,在菜单中选择Run : Run。在MonoTouch的评估版中,你只能在模拟器中运行,如果你打算在iPhone中运行,你会得到一个错误。

如果一切正常,模拟器会显示出来(实际上,它有可能隐藏在MonoDevelop窗口的背后,所以你需要切换过去),那么你就能看到如下所示的效 果:

点击按钮将会产生下图的结果:

恭喜你!你已经创建并跑起你的第一个iPhone应用程序了。

Actions(动作)

在我们刚刚创建的应用程序中,我们有一些Outlets,在代码中可以藉由属性来访问控件。就像在其他.NET GUI模型中,我们能把事件处理程序挂接到它们之上,来对事件作出响应。不过MonoTouch提供了另外一种响应用户输入的方式。这称之为 Actions。Actions类似于WPF的Commands,用这种方式,多个控件可以调用同一个方法,然后依据调用者是谁来决定如何去处理。让我们 来稍微仔细地研究一下。确保你已经在MonoDevelop打开了Example_HelloWorld_1应用程序了。

双击MainWindow.xib文件来在Interface Builder中打开它。现在在标签控件下面添加两个按钮,类似下图:

再次,我们要针对Leopard和Snow Leopard作出不同的说明,你要确定按照正确的部分进行操作。

【SNOW LEOPARD指示开始】

在Library窗口中,确保选中“Classes”标签页,并再次在顶部的下拉列表框中选择“Other Classes”。接着,在上面选择AppDelegate,在下面选择“Actions”标签页。创建一个名为ActionButtonClick的 Action。你的Library窗口应该看起来如下所示:

【SNOW LEOPARD指示结束】

【LEOPARD指示开始】

在窗口管理器中,确保选中App Delegate 。接着在Identity Inspector窗口中,在“Class Actions”里面创建一个名为ActionButtonClick的Action。你的Identity Inspector应该看起来如下所示:

【LEOPARD指示结束】

我们刚刚在App Delegate上创建好了一个名为ActionButtonClick的通用动作。现在,要做的就是把它关联到按钮的 TouchDown事件上,以便在按钮被点击的时候,这个Action能被调用。

首先,选择一个动作按钮,接着转到Connections Inspector,把Touch Down拖动Document窗口中的App Delegate。如下图所示:

注意,在我们拖到App Delegate的时候,会显示出一个可用Actions的列表。选择ActionButtonClick,现在按钮上的TouchDown事件就关联到 这个动作上了:

为两个动作按钮都进行同样的操作。如果我们在Connections Inspector中查看App Delegate,会这样显示:

通过点击“Multiple”旁边的箭头,可展开和ActionButtonClick相关的控件。保存好.xib文件后,回到 MonoDevelop中。

如果我们看一下MainWindow.designer.cs,会发现多了一行新代码:

[MonoTouch.Foundation.Export("ActionButtonClick")] 

partial void ActionButtonClick (MonoTouch.UIKit.UIButton sender);

这是我们的Action的分部声明。注意它用MonoTouch.Foundation.Export特性标记进行了装饰。这使 Objective-C运行时可以找到关联到我们Action上的适当方法。编译器实际上会忽略没有任何实现的分部方法(正如我们在这里所见的这个),那 么这个分部方法的声明实际上是为了在实现它的时候可以获得代码完成的功能。如果我们回到Main.cs,会看到它的真正作用。在AppDelegate类 中,只要键入“partial”就会自动地得到ActionButtonClick的自动完成代码:

我们把如下代码写在里面:

partial void ActionButtonClick (UIButton sender)

{
	//---- show which button was clicked
	this.lblResult.Text = sender.CurrentTitle + " Clicked";
}

现在,如果你运行应用程序,在动作按钮上点击,将会看到如下所示的效果:

此时,我们已经完整地经历了用MonoTouch来开发基本iPhone应用程序的过程。现在,你应该对MonoTouch应用程序的结构、利用事 件处理用户交互,以及用Actions来处理用户交互有了基本的了解。

不过,还有一件重要的事情被忽略了,我们应用程序只具有一个界面。在下一篇文章《The Model-View-Controller Pattern in MonoTouch》中,我们将会谈及具有多个界面的应用程序。

示 例代码

查看英文原文:MonoTouch: .NET Development for the iPhone

[转载]使用jQuery.AutoComplete完成仿淘宝商品搜索功能(改进了键盘上下选择体验)

mikel阅读(1099)

[转载]使用jQuery.AutoComplete完成仿淘宝商品搜索功能(改进了键盘上下选择体验) – kyo-yo – 博客园.

其实这个已经是个比较常见的功能了,网上也有很多人做过这个了,但是很多都是仅仅做了一些基本的网页上自动完成功能,没有与具体的数据库进行联动,我今天所介绍这个自动完成的就是我修改的JQuery.AutoComplete+数据库的一个解决方案

首先来看一些效果图:

pic1

这个是淘宝首页的搜索效果

pic3

京东首页的搜索效果

pic2

我修改的jQuerzy.AutoComplete实现的效果

一、实现效果分析

我要实现的效果就是和GOOGLE类似,需要满足一下3个要求(因为这样我认为是最好的用户体验,毕竟GOOGLE做了那么久了):

1、首先根据关键字列出关键字相关的信息(包含统计信息)

2、可以使用键盘上下键选择(默认不选中第一条),文本框内容根据选择信息变换

3、当选择第一或者最后一条时再向上或向下则取消选中,文本框中内容还原回原先输入的内容(这点比较重要,京东这个就做不好,因为当在向上向下选择的过程中因为文本框内容会跟着换,所以就无法还原到当初用户所输入的内容了)

二、具体实现分析

首先呢因为具体数据时来自于数据库,所以首先在数据库中建立张表用于存放搜索历史记录,每次用户查询的其实就是数据库中的表的记录(也就是上次查询这个关键字的记录数)

1 CREATE TABLE [dbo].[t_KeywordSearchHistory] (
2 [Keyword] [nvarchar] (128) primary key, --关键字
3 [Count] [int] NOT NULL , --搜索次数
4 [RecordCount] [int] NOT NULL --符合关键字的记录数
5 )

上面的表仅仅用于存放用户搜索的关键字,然后在搜索的存储过程或者SQL语句中才进行相应的处理,当用户在页面上输入完关键字然后再点击搜索此时需 要首先根据关键字在数据库中检索相应的数据,若此关键字有相关数据则向t_KeywordSearchHistory表新增一条数据(若此表中已有此关键 字则更新搜索次数和符合关键字的记录数)

01 --上面的是具体的SQL查询代码(统计符合关键字的商品数量
02 if @recordCount>0
03 begin
04 if @keyword <>''
05 begin
06 if exists (select keyword from t_KeywordSearchHistory where keyword=@keyword)
07 begin
08 update t_KeywordSearchHistory set
09 Count=Count+1,
10 RecordCount=@recordCount
11 where keyword=@keyword
12 end
13 else
14 begin
15 insert into t_KeywordSearchHistory values(@keyword,1,@recordCount)
16 end
17 end
18 end
19 else
20 begin
21 update t_KeywordSearchHistory set Count=Count+1,
22 RecordCount=@recordCount
23 where keyword=@keyword
24 end

完成了数据库方面的相关代码后就是界面上的,首先是JQuery.AutoComplete的调用方法:

1 jQuery(function(){
2 jQuery("#txtKeyword").autocomplete("<%=Me.Page.ResolveClientUrl("~/Service.asmx/AutoComplete") %>", {
3 httpMethod: "POST", //使用POST调用WebService
4 dataType: 'xml',//返回数据类型为XML
5 minchar: 1,//最小响应字符数量
6 selectFirst:false,//默认不选中第1条
1 //格式化选项,由于WebService返回的数据是JSON格式,现在要转成HTML以TABLE形式显示
2 formatItem:function(row,i,max){
1 var obj=eval("("+row+")");//将JSON转换成对象
2 var item="<table id='auto"+i+"' style='width:100%;'>
3 <tr>
4 <td align='left'>"+obj.value+"</td>
5 <td align='right' style='color:green;'>"+obj.num+"</td>
6 </tr>
7 </table>";
8 return item;
9 },
1 //格式化结果,当选中时返回具体的值
2 formatResult:function(row,i,max){
3 var obj=eval("("+row+")");
4 return obj.value;
5 }
6 });
7 });

WebService代码:

1 [WebMethod()]
2 public string[] GetGoodsAutoComplete(string q)
3 {
4 List<string> list = new List<string>();
01 //JSON格式模板,同时以换行符分隔,在JS脚本中会进行处理
02 string template = "{{value:'{0}',num:'{1}'}}" + System.Environment.NewLine;//+”\n”
03 SqlCommand cmd = new SqlCommand();
04 SqlDataReader reader = null;
05 cmd.CommandText = "GetAutoComplete";
06 cmd.CommandType = CommandType.StoredProcedure;
07 cmd.Parameters.Add("@keyword", SqlDbType.NVarChar, 128).Value = q;
08 try {
09 reader = Tools.Data.SQLServerHelper.GetReader(VolkHelper.GetDBConnString(), cmd);
10 if (reader != null) {
11 while (reader.Read()) {
12 string s = string.Format(template, (string)reader["keyword"], "约" + (string)reader["num"] + "件商品");
13 list.Add(s);
14 }
15 }
16 }
17 catch (Exception ex) {
18
19 }
20 return list.ToArray();
21 }

接下来就是我修改的jQuery.AutoComplete.js,由于代码太长,我在文章最后已经加了下载的链接所以就不把代码全部贴出来了,仅贴我修改的地方:

1 function moveSelect(step) {
2 listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
3 movePosition(step);
4 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
5 //当动作对象为空时还原用户输入的值
01 if (activeItem[0] != null || activeItem[0] != undefined) {
02 input.value = jQuery(activeItem[0]).find("td:first").text();
03 }
04 if (active >= 0) {
05 if (options.scroll) {
06 var offset = 0;
07 listItems.slice(0, active).each(function() {
08 offset += this.offsetHeight;
09 });
10 if ((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
11 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
12 } else if (offset < list.scrollTop()) {
13 list.scrollTop(offset);
14 }
15 }
16 }
17 };
18
19 function movePosition(step) {
20 if (active < 0 && step == -1) {
21 active = listItems.size()-1;
22 return;
23 }
24 active += step;
1 //光标不再列表时还原用户输入的值
2 if (active < 0) {
3 active = -1;
4 input.value = oldValue;
5 return;
6 }
7 //超出关键字列表时还原用户输入的值
1 if (active >= listItems.size()) {
2 active = -1;
3 input.value = oldValue;
4 return;
5 }
6 }

已经684行开始:

01 next: function() {
02 if (active == -1) {
03 oldValue = input.value;//一开始将用户输入的值存入一个指定的变量
04 }
05 moveSelect(1);
06 },
07 prev: function() {
08 if (active == -1) {
09 oldValue = input.value;
10 }
11 moveSelect(-1);
12 },

以上就完成了自动完成的全部的必须条件了,如果对jQuery.Autocomplete不熟悉的话可以去这里看下具体的使用方法。我在这就不详细说明了。

有网友反映jQuery.AutoComplete无法调试成功,本来在写文章中忘了注明了,就是在webservice中需要添加这个配置节:

01 在system.web配置节下添加:
02 <!--webservice设置-->
03 <webServices>
04 <protocols>
05 <add name="HttpPost"/>
06 <!--<add name="HttpGet"/>
07 <add name="HttpSoap"/>
08 <add name="Documentation"/>-->
09 </protocols>
10 </webServices>

同时在webservice中需要为webservice中添加以下特性:

1 [System.Web.Script.Services.ScriptService()]
2 [WebService(Namespace = http://tempuri.org/)]
3 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

其中[System.Web.Script.Services.ScriptService()]特性需要VS2008才拥有,否则是无法调试成功的

附我修改的jQuery.AutoComplete.rar下载:点我下载

PS:大家如果觉得好帮忙点下推荐,谢谢大家了!

[转载]用SMO(sql server management object)生成创建表的脚本

mikel阅读(982)

[转载]用SMO(sql server management object)生成创建表的脚本 – 从挨踢到IT – 博客园.

我本人是做C# winform 开发的。

最近接触一个任务。要根据SQL里一个现有的表生成创建这个表的脚本。

先是想从存储过程的角度解决问题

在网上看了一些资料,发现MSSQL本身没有这个功能,要自己去写。

而其他的数据库对象(视图,存储过程,触发器)则可以通过 SPHelp_Text来解决。

查一些资料无果后,决定自己研究一下。

看了一下SQL的联机文档 ,发现有SMO开发的方式。

如果要生成脚本,需要的引用 为

C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.ConnectionInfo.dll

C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.Management.Sdk.Sfc.dll

C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.Smo.dll

C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.SmoExtended.dll

C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.SqlEnum.dll

下面做一个抛砖引玉,贴一下生成脚本的源码

代码


using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using System.Collections.Specialized;

namespace geyunfei
{
class Program
{
static void Main(string[] args)
{

//初始化一个连接
Server server = new Server(new ServerConnection(
localhost, sa, “”));
//得到数据库
var srcDb = server.Databases[TestDB];
//得到表
Table table = srcDb.Tables[Table1];

//初始化Scripter
Scripter a = new Scripter();
a.Options.Add(ScriptOption.DriAllConstraints);
a.Options.Add(ScriptOption.DriAllKeys);
a.Options.Add(ScriptOption.Default);
a.Options.Add(ScriptOption.ContinueScriptingOnError);
a.Options.Add(ScriptOption.ConvertUserDefinedDataTypesToBaseType);
a.Options.Add(ScriptOption.IncludeIfNotExists);

UrnCollection collection = new UrnCollection();

collection.Add(table.Urn);

var sqls = a.Script(collection);

foreach (var s in sqls)
{
System.Console.WriteLine(s);
}

}
}
}

附一些没用的话:

怎么找到使用SMO这个方式的。。。

1.接到任务,第一反应,存储过程。在网上查了一堆资料,发现都是程序员自己写的,MS没有提供类似于Sphelp_text的方法,虽然也可以 用,但是大多针对 特定版本的数据库,不好维护。

2.转而准备使用反应,用SQL TO Linq的文件生成脚本,但是,仍然上个原因,不好维护这个东西。

3.想起来Sqlserver mangement studio里可以生成脚本 。于是自己翻SSMS的动态 连接库,根据 名称试了几个DLL的名称,太复杂 ,没有搞定。

4.于是开始查SQL的联机文档,发现有这个功能,试了一下,还真好用,嘿嘿。

[转载]SQL CODE

mikel阅读(1313)

[转载]【汇总】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阅读(1098)

[转载]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阅读(1006)

[转载]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阅读(1466)

[转载]白板报 » 我是如何解决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阅读(2596)

最近需要做打印发货单的功能,以前用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阅读(932)

[转载]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阅读(949)

[转载]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