2006年09月19日

ASP.NET 2.0 客户端无刷新调用服务器端方法获取数据

2005-10-28 18:23:00

 

制约B/S程序的一个重要原因可能归结於Http协议的无状态性,每当我们要客户端从服务器上获取数据或想执行一段服务器端代码,我们必须先提交页面,在页面提交后,事件句柄使代码执行并把结果返回给提交的客户端,这个事件驱动模式在大部分情况下没有问题,但是它却强制ASP.NET开发者必须多做一些事情。为了保存窗体上的控件状态,开发人员要么使用大量降低页面速度的ViewState,要么得写一些复杂的程序逻辑,而且这也使浏览器增加了处理时间。在小页面上,这没什么问题,但是在一些大量DHTML应用的页面,或内容很多的页面上,宽带用户就会感觉到“闪”,而拨号用户甚至会看见状态栏中蓝色的进度条。所以,如果能在客户端调用服务器端代码将是一件美好的事情,而ASP.NET 2.0 使其成为现实。ASP.NET 2.0有一个受关注的焦点是客户端回访功能,也就是说在页面上用客户端的Javascript代码调用服务器端代码而不需要页面回传。在过去,TreeView控件的频繁刷新页面已经让我们无法忍受,但是现在因为有了Client Callback,我们可以告别这一现象了。

在了解回访技术前,我们来看看现在通常程序员是怎么做的。由javascript访问服务器端代码通常求助于微软的XMLHTTP ActiveX组件。这个组件允许你通过http协议通过互联网获取xml文档,然而并不像和它的名字所表示的那样,其实你可以用这个组件来对任何服务器发送http请求,包括老式的asp,标准html,甚至php文件,而仅仅收回一个未处理的html输出。因为这个组件几乎是一个标准的ActiveX组件,你可以用标准的javascript语法来实例化它:
function RetrieveGoogleFrontPage()
{  
 var XmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");  
 XmlHttp.Open("GET", "http://www.google.com", false);  
 XmlHttp.Send();  
 return XmlHttp.responseText;
}
我们看到代码非常的简单,而所有代码都是javascript中执行,不需要重新提交页面,也就不会再闪了,但是请注意到,这个组件获取的是整个页面,所以如果你需要的仅仅是一部分数据,你需要单独写一个页面来返回你需要的数据。

我们来看看ASP.NET 2.0中的Client Callback,其实在底层同样是使用了XmlHttp技术,只不过封装起来,对
程序员屏蔽了。Client Callback由2部分组成:新的ICallbackEventHandler接口和Page类新的静态方法
GetCallbackEventReference()。Page.GetCallbackEventReference()方法及其重载生成了客户端所需要的javascript代码段,这些代码在底层使用了XmlHttp组件发出了一个http请求,而服务器端通过一个实现了ICallbackEventHandler接口的控件接受它,通常这个控件就是页面自己本身,但是你也可以明确地用你自己的用户控件或web控件对这个请求进行响应。当请求返回时,结果是通过另外一个javascript函数来接受的。我们来看一个从服务器获取系统时间并在客户端alert出来的代码:
<%@ page language="C#" compilewith="ServerTime.aspx.cs" classname="ASP.ServerTime_aspx"%>
<html>  
 <head>     
 <title>Server Time</title>     
 <script language="javascript">        
  function GetServerTime()        
  {           
      var message = ”;
      var context = ”;                              
      <%=sCallBackFunctionInvocation%>        
  }                 
  function ShowServerTime(timeMessage, context)
  {           
   alert(‘The time on the server is:\n’ + timeMessage);        
  }                 
  function OnError(message, context)
  {           
   alert(‘An unhandled exception has occurred:\n’ + message);
   }     
 </script>  
 </head>
<body>  
<form id="MainForm" runat="server">     
<input type="button" value="Get Server Time" onclick="GetServerTime();" />   </form>
</body>
</html>
using System;
using System.Web.UI;
namespace ASP
{
public partial class ServerTime_aspx : ICallbackEventHandler  
{      public string sCallBackFunctionInvocation;
      void Page_Load(object sender,System.EventArgs e)
      {
         sCallBackFunctionInvocation =this.GetCallbackEventReference(this,"message","ShowServerTime","context","OnError");
      }
      public string RaiseCallbackEvent(string eventArgument)
      {        
         return DateTime.Now.ToString();
      }
   }
}

这里注意到页面实现了ICallbackEventHandler接口,这个接口只有一个方法RaiseCallbackEvent,这个方法就是页面接受到一个回调请求时所执行的代码在这个例子中,简单地返回一个系统时间。为了产生客户端调用代码,我们在Page_Load中调用了 Page.GetCallbackEventReference方法,这个方法有很多重载,但都包含了以下参数:即将接受请求的控件(在这里是当前页),包含参数值的客户端变量名,以及请求返回或出错时要执行的客户端函数。返回值是生成的javascript代码,在页面上的<%=sCallBackFunctionInvocation%>在页面执行时将被替换为:
__doCallback(‘__Page’,message,ShowServerTime,context,OnError)
__doCallback是ASP.NET 2.0的一个公共方法,用于向服务器回发请求。

从上图中可以看出这种调用的过程。

 

注意:本文内容来自《ASP.NET 2.0’s Client Callback Feature》(英文)一文,原文来自网络,作者未知,翻译时有删节并对一些段落加入解释与调整。

2006年09月15日

 

2005.10.28  来自:MSDN  Karl Seguin

Asynchronous JavaScript and XMLAJAX)最近掀起的高潮,要完全归功于GoogleGoogle SuggestGoogle Maps中的使用。对ASP.NET而言,AJAX不需要回传就能进行服务器端处理,从而使客户机(浏览器)具有丰富的服务器端能力。换句话说,它为异步指派和处理请求与服务器响应提供了一个框架。AJAX利用了一些不是很新颖的已有技术,但是对这些技术(加到一起就是AJAX)的爱好最近突然升温。

请尝试Michael Schwarz的AJAX .NET包装器,通过它ASP.NET开发人员可以快速方便的部署很容易利用AJAX功能的页面。需要注意的是,这个包装器处于初期开发阶段,因此还没有完全成熟。

然而,AJAX这样的技术很可能破坏分层体系结构(N-Tier)。我的看法是,AJAX增加了表示逻辑层(甚至更糟,业务层)渗透到表示层的可能性。像我这样严肃的架构师对这种想法可能畏步不前。我感到AJAX的使用即便稍微越过了层次边界,这种代价也是值得深思的。当然,这要视具体的项目和环境而定。

起步

它是如何工作的——概述

AJAX依靠代理(broker)指派和处理往返服务器的请求。对此,.NET包装器依靠客户端XmlHttpRequest对象。多数浏览器都支持XmlHttpRequest对象,这就是选择它的原因。因为包装器的目的是隐藏XmlHttpRequest的实现,我们就不再详细讨论它了。

包装器本身通过将.NET函数标记为AJAX方法来工作。标记之后,AJAX就创建对应的JavaScript函数,这些函数(和任何JavaScript函数一样)作为代理可以在客户端使用XmlHttpRequest调用。这些代理再映射回服务器端函数。

复杂吗?并不复杂。我们来看一个例子。假设有一个.NET函数:

ublic int Add(int firstNumber, int secondNumber)
{
 
return firstNumber + secondNumber;
}

AJAX .NET包装器将自动创建名为“Add”、带有两个参数的JavaScript函数。使用JavaScript(在客户机上)调用该函数时,请求将传递给服务器并把结果返回给客户机。

初始设置

我们首先介绍“安装”项目中使用的.dll的步骤。如果您很清楚如何添加.dll文件引用,可以跳过这一节。

首先,如果还没有的话,请下载最新的AJAX版本。解压下载的文件并把ajax.dll放到项目的引用文件夹中。在Visual Studio.NET中有机Solution Explorer的“References(引用)”节点并选择Add Reference(添加引用)。在打开的对话框中,单击Browse(浏览)并找到ref/ajax.dll文件。依次单击Open(打开)和Ok(确认)。这样就可以用AJAX .NET包装器编程了。

建立HttpHandler

为了保证正常工作,第一步是在web.config中设置包装器的HttpHandler。不需要详细解释HttpHandlers是什么及其如何工作,只要知道它们用于处理ASP.NET请求就足够了。比如,所有*.aspx页面请求都由System.Web.UI.PageHandlerFactory类处理。类似的,我们让所有对ajax/*.ashx的请求由Ajax.PageHandlerFactory处理:

<configuration>
 
<system.web>
   
<httpHandlers>
     
<add verb="POST,GET" path=" ajax/*.ashx"
         
type="Ajax.PageHandlerFactory, Ajax" />
    </httpHandlers> 

   

 
<system.web>
</configuration>

简言之,上面的代码告诉ASP.NET,和指定路径(ajax/*.ashx)匹配的任何请求都由Ajax.PageHandlerFactory而不是默认处理程序工厂来处理。不需要创建ajax子目录,使用这个神秘的目录只是为了让其他HttpHandlers能够在自己建立的子目录中使用.ashx扩展。

建立页面

现在我们可以开始编码了。创建一个新页面或者打开已有的页面,在file后的代码中,为Page_Load事件添加以下代码:

public class Index : System.Web.UI.Page{
 
private void Page_Load(object sender, EventArgs e){
      Ajax.Utility.RegisterTypeForAjax(typeof(Index));     

      //…   

 
}
  //… 

}

调用RegisterTypeForAjax将在页面上引发后面的JavaScript(或者在页面中手工加入以下两行代码):

<script language="javascript" src="ajax/common.ashx"></script>
<script language="javascript"
src="ajax/Namespace.PageClass, AssemblyName.ashx"></script>

其中最后一行的含义是:

下面是AjaxPlay项目中sample.aspx页面的结果例子:

<%@ Page Inherits="AjaxPlay.Sample" Codebehind="sample.aspx.cs" … %>
<html>
<head>
 
<script language="javascript" src="ajax/common.ashx"></script>
 
<script language="javascript"
         
src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script>
</head>
  <body>   

   
<form id="Form1" method="post" runat="server">
     

    </form>   

 
</body>
</html>

可以在浏览器中手工导航到src路径(查看源代码,复制粘贴路径)检查是否一切正常。如果两个路径都输出一些(似乎)毫无意义的文本,就万事大吉了。如果什么也没输出或者出现ASP.NET错误,则表明有些地方出现问题。

即便不知道HttpHandlers如何工作,上面的例子也很容易理解。通过web.config,我们已经保证所有对ajax/*.ashx的请求都由自定义的处理程序处理。显然,这里的两个脚本标签将由自定义的处理程序处理。

创建服务器端函数

现在来创建可从客户端调用中异步访问的服务器端函数。因为目前还不支持所有的返回类型(不用担心,将在目前的基础上开发新的版本),我们继续使用简单的ServerSideAdd函数吧。在file后的代码中,向页面添加下列代码:

[Ajax.AjaxMethod()]
public int ServerSideAdd(int firstNumber, int secondNumber)
{
 
return firstNumber + secondNumber;
}

要注意,这些函数具有Ajax.AjaxMethod属性集。该属性告诉包装器这些方法创建javaScript代理,以便在客户端调用。

客户端调用

最后一步是用JavaScript调用该函数。AJAX包装器负责创建带有两个参数的JavaScript函数Sample.ServerSideAdd。对这种最简单的函数,只需要调用该方法并传递两个数字:

<%@ Page Inherits="AjaxPlay.Sample" Codebehind="sample.aspx.cs" … %>
<html>
<head>
 
<script language="javascript" src="ajax/common.ashx"></script>
 
<script language="javascript"
         
src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script>
</head>
  <body>   

   
<form id="Form1" method="post" runat="server">
     
<script language="javascript">
       
var response = Sample.ServerSideAdd(100,99);
       
alert(response.value);
     
</script>
    </form>   

 
</body>
</html>

当然,我们不希望仅仅用这种强大的能力来警告用户。这就是所有客户端代理(如JavaScript Sample.ServerSideAd函数)还接受其他特性的原因。这种特性就是为了处理响应而调用的回调函数:

Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack);

function ServerSideAdd_CallBack(response){
 
if (response.error != null){
 
alert(response.error);
 
return;
 
}
 
alert(response.value);
}

从上述代码中可以看到我们指定了另外一个参数。ServerSideAdd_CallBack(同样参见上述代码)是用于处理服务器响应的客户端函数。这个回调函数接收一个响应对象,该对象公开了三个主要性质

  • Value——服务器端函数实际返回的值(无论是字符串、自定义对象还是数据集)。
  • Error——错误消息,如果有的话。
  • Request——xml http请求的原始响应。
  • Context——上下文对象。

首先我们检查error只看看是否出现了错误。通过在服务器端函数中抛出异常,可以很容易处理error特性。在这个简化的例子中,然后用这个值警告用户。Request特性可用于获得更多信息(参见下一节)。

处理类型

返回复杂类型

Ajax包装器不仅能处理ServerSideAdd函数所返回的整数。它目前还支持integersstringsdoublebooleansDateTimeDataSetsDataTables,以及自定义类和数组等基本类型。其他所有类型都返回它们的ToString值。

返回的DataSets和真正的.NET DataSet差不多。假设一个服务器端函数返回DataSet,我们可以通过下面的代码在客户端显示其中的内容:

<script language="JavaScript">
//Asynchronous call to the mythical "GetDataSet" server-side function
function getDataSet(){
  AjaxFunctions.GetDataSet(GetDataSet_callback);   

}
function GetDataSet_callback(response){
 
var ds = response.value;
 
if(ds != null && typeof(ds) == "object" && ds.Tables != null){
   
var s = new Array();
   
s[s.length] = "<table border=1>";
   
for(var i=0; i<ds.Tables[0].Rows.length; i++){
     
s[s.length] = "<tr>";
     
s[s.length] = "<td>" + ds.Tables[0].Rows[i].FirstName + "</td>";
     
s[s.length] = "<td>" + ds.Tables[0].Rows[i].Birthday + "</td>";
     
s[s.length] = "</tr>";
   
}
   
s[s.length] = "</table>";
   
tableDisplay.innerHTML = s.join("");
 
}
 
else {
   
alert("Error. [3001] " + response.request.responseText);
 
}
}
</script>

Ajax还可以返回自定义类,唯一的要求是必须用Serializable属性标记。假设有如下的类:

[Serializable()]
public class User{
 
private int _userId;
 
private string _firstName;
 
private string _lastName;

  public int userId{
   
get { return _userId; }
 
}
 
public string FirstName{
   
get { return _firstName; }
 
}
 
public string LastName{
   
get { return _lastName; }
 
}
 
public User(int _userId, string _firstName, string _lastName){
   
this._userId = _userId;
   
this._firstName = _firstName;
   
this._lastName = _lastName;
 
}
 
public User(){}
 
[AjaxMethod()]
 
public static User GetUser(int userId){
   
//Replace this with a DB hit or something :)
   
return new User(userId,"Michael", "Schwarz");
 
}
}

我们可以通过调用RegisterTypeForAjax注册GetUser代理:

private void Page_Load(object sender, EventArgs e){
 
Utility.RegisterTypeForAjax(typeof(User));
}

这样就可以在客户端异步调用GetUser

<script language="javascript">
function getUser(userId){
 
User.GetUser(GetUser_callback);
}
function GetUser_callback(response){
 
if (response != null && response.value != null){
   
var user = response.value;
    if (typeof(user) == "object"){         

     
alert(user.FirstName + " " + user.LastName);
   
}
 
}
}
getUser(1);
</script>

响应中返回的值实际上是一个对象,公开了和服务器端对象相同的属性(FirstNameLastNameUserId)。

自定义转换器

我们已经看到,Ajax .NET包装器能够处理很多不同的.NET类型。但是除了大量.NET类和内建类型以外,包装器对不能正确返回的其他类型仅仅调用ToString()。为了避免这种情况,Ajax .NET包装器允许开发人员创建对象转换器,用于在服务器和客户机之间平滑传递复杂对象。

其他事项

在其他类中注册函数

上面的例子中,我们的服务器端函数都放在执行页面背后的代码中。但是,没有理由不能把这些函数放在单独的类文件中。要记住,包装器的工作方式是在指定类中发现所有带Ajax.AjaxMethod的方法。需要的类通过第二个脚本标签指定。使用Ajax.Utility.RegisterTypeForAjax,我们可以指定需要的任何类。比如,将我们的服务器端函数作为单独的类是合情合理的:

Public Class AjaxFunctions
 
<Ajax.AjaxMethod()> _
 
Public Function Validate(username As String, password As String) As Boolean
   
‘do something
   
‘Return something
 
End Function
End Class

通过指定类的类型而不是页面就可以让Ajax包装器创建代理:

private void Page_Load(object sender, EventArgs e){
 
Ajax.Utility.RegisterTypeForAjax(typeof(AjaxFunctions));
 
//…
}

要记住,客户端代理的名称是<ClassName>.<ServerSideFunctionName> 。因此,如果ServerSideAdd函数放在上面虚构的AjaxFunctions类中,客户端调用就应该是: AjaxFunctions.ServerSideAdd(1,2)

代理到底是如何工作的

Ajax工具生成的第二个脚本标签(也可以手工插入)传递了页面的名称空间、类名和程序集。根据这些信息,Ajax.PageHandlerFactory就能够使用反射得到具有特定属性的任何函数的详细信息。显然,处理函数查找具有AjaxMethod属性的函数并得到它们的签名(返回类型、名称和参数),从能够创建必要的客户端代理。具体而言,包装器创建一个和类同名的JavaScript对象,该对象提供代理。换句话说,给定一个带有Ajax ServerSideAdd方法的服务器端类AjaxFunctions,我们就会得到公开ServerSideAdd函数的AjaxFunction JavaScript对象。如果将浏览器指向第二个脚本标签的路径就会看到这种动作。

返回Unicode字符

Ajax .NET包装器能够从服务器向客户机返回Unicode字符。为此,数据在返回之前必须在服务器上用html编码。比如:

[Ajax.AjaxMethod]
public string Test1(string name, string email, string comment){
 
string html = "";
 
html += "Hello " + name + "<br>";
 
html += "Thank you for your comment <b>";
 
html += System.Web.HttpUtility.HtmlEncode(comment);
 
html += "</b>.";
 
return html;
}

SessionState

服务器端函数中很可能需要访问会话信息。为此,只需要通过传递给Ajax.AjaxMethod属性的一个参数告诉Ajax启用这种功能。

在考察包装器会话能力的同时,我们来看看其他几个特性。这个例子中我们有一个文档管理系统,用户编辑的时候会对文档加锁。其他用户可以请求在文档可用的时候得到通知。如果没有AJAX,我们就只能等待该用户再次返回来检查请求的文档是否可用。显然不够理想。使用支持会话状态的Ajax就非常简单了。

首先来编写服务器端函数,目标是循环遍历用户希望编辑的documentId(保存在会话中)并返回所有已释放的文档。

[Ajax.AjaxMethod(HttpSessionStateRequirement.Read)]
public ArrayList DocumentReleased(){
 
if (HttpContext.Current.Session["DocumentsWaiting"] == null){
   
return null;
 
}
 
ArrayList readyDocuments = new ArrayList();
 
int[] documents = (int[])HttpContext.Current.Session["DocumentsWaiting"];
 
for (int i = 0; i < documents.Length; ++i){
   
Document document = Document.GetDocumentById(documents[i]);
   
if (document != null && document.Status == DocumentStatus.Ready){
     
readyDocuments.Add(document);
    }       

 
}
 
return readyDocuments;
 
}
}

要注意,我们指定了HttpSessionStateRequirement.Read值(还可以用WriteReadWrite)。

现在编写使用该方法的JavaScript

<script language="javascript">
function DocumentsReady_CallBack(response){
 
if (response.error != null){
   
alert(response.error);
   
return;
 
}
 
if (response.value != null && response.value.length > 0){
   
var div = document.getElementById("status");
   
div.innerHTML = "The following documents are ready!<br />";
   
for (var i = 0; i < response.value.length; ++i){
     
div.innerHTML += "<a href=\"edit.aspx?documentId=" + response.value[i].DocumentId + "\">" + response.value[i].Name + "</a><br />";
    }     

 
}
 
setTimeout(‘page.DocumentReleased(DocumentsReady_CallBack)’, 10000);
}
</script>
 

<body onload="setTimeout(‘Document.DocumentReleased(DocumentsReady_CallBack)’, 10000);">

我们的服务器端函数在页面加载时调用一次,然后每隔10秒钟调用一次。回调函数检查响应看看是否有返回值,有的话则在div标签中显示该用户可使用的新文档。

结束语

AJAX技术已经催生了原来只有桌面开发才具备的健壮而丰富的Web界面。Ajax .NET包装器让您很容易就能利用这种新的强大技术。请注意,Ajax .NET包装器和文档仍在开发之中。

2006年06月01日

好长时间没有来更新我的博客了,好像我差点把它给忘了似的。

写点什么呢?唉,不知道。最近的事情比较多:山西左云矿难,官商勾结,草菅人命;国六条调控房价,打击无良开发商;陈水扁决定放权……好像这些跟我都没什么关系,除了房价这个跟我有一点点关系,虽然我近期内也没有买房的打算。

开发暂时告一段落,准备提交测试组进行测试;头头提了新的需求,要求图形显示某个东西,天哪,这么相当有难度的活,唉,没有办法;这几天连续几个同事离职,告别信一封一封的砸在信箱里面;我可怜的酸菜贪嘴中毒而亡,享年大概60天左右;我的腰在我完全不之情的状况下扭了,躺在床上起不来,据说吐气吐岔了也能把腰扭着,这么诡异的事情怎么会被我碰上?!这些其实都没什么。

生活,其实就是一锅菠菜汤,即使你觉得它咸了,也有人觉得它其实还淡。

2006年05月17日
另类的VS.NET 的资源

(日期:2005年3月12日 作者: 人气: 196 查看:[大字体 中字体 小字体])

突然发现自己IE的Favorites中有关dotnet的链接慢慢的多起来,从开始的一两个到现在越来越多的链接。我想,是因为有越来越多的人开始对dotnet感兴趣了,比如C#、Web Service、XML。我很喜欢现在VS.NET的IDE,除了需要更多的内存外,功能很丰富,而且越来越智能。但除了MS网站上的说明,很难找到其他的信息和资源,我找到和收集了一些,希望对你有用:

CodeSwap :

今天我安装了这个小东东,这样我可以将自己的代码发布到网络中,而且所有安装了这个软件的人都可查找和共享这些代码。就像找寻mp3歌曲一样,我输入了一个“API”果然有这样标题的文章存在,甚至你可以投票表示你的意见。最有意思的是它也是一个Web   Service ,目前收集的文章还不是很多,但这种类似P2P的模式让我有一种不同的沟通方式。
你可以去下面的网址去看看或直接下载,网站上只有索引,当你安装完成之后,就可以在VS.NET中下载和使用这些代码了。

URL:
http://www.vscodeswap.net
http://www.vscodeswap.net/codeswapaddin.msi

http://www.vscodeswap.net
http://www.vscodeswap.net/codeswapaddin.msi


VS.NET beta2 Add-in:

国外有许多公司在做MS编程工具的Add-in或辅助工具,MS也很早发表了VS.NET的DIA SDK,最早在Beta1发表时就看到了Visual XSLT 的Add-in,Beta2的时候Crystal Reports 成了缺省的选项被安装在VS.NET中。据MS的资料显示说有一些有关Automation samples的例子在Visual Studio .NET Beta 2 Professional Edition中是被遗漏了,但我在企业版中似乎也没有发现这些例子,不过我在这里发现这些例子和另外一些有趣的东西,里面还包括一些MS网站上dotnet的例子比如:VBCSharpwalkthroughs、IBuySpy,值得Download当然也包括一个Viso的帮助zip。MS的目录中没有任何说明,我附带了说明在后面,这样你大致知道那些Zip包都是干什么的了。提醒你要去赶快去,已经有一段时间了,不知MS会不会改Password。
http://beta.visualstudio.net/
如果需要你登陆那么你可以访问这个URL
ftp://vstudio:7^Qs4*!8@transfer1.microsoft.com/BetaDownload/vstudio/

Automation samples 清单:
Spell Check Macro
This is a macro that does spell-checking uses Word and C# Windows Forms for
the UI.

ToolWindow Add-ins
This collection of three add-ins demonstrates how to create ToolWindows in
C#, Visual Basic .NET and C++.

PostBuildRules Add-in
This add-in allows the user to create rules that are applied after the
solution build completes. This Visual Basic .NET add-in demonstrates the use
of events and the globals object in the Visual Studio .NET Automation Model.

PreBuildRules Add-in
This add-in allows the user to create rules that are applied before the
solution build starts. This C# add-in demonstrates the use of events and the
globals object in the Visual Studio .NET Automation Model.

Solution Extender Add-in
This sample demonstrates the use of Automation Extenders to "extend"
existing Visual Studio objects with new properties and methods. You can
extend property grid display objects as well as automation objects so that
more methods and properties are available to the caller. This sample extends
both the Solution property grid display object as well as the DTE.Solution
automation object.

WSH Script
The wsh-vs.js sample demonstrates using Windows Scripting Host to launch
Visual Studio. It will create an instance of VS, and add a task item. To use
this sample, just double click on it from explorer, or type wsh-vs.js at a
Windows command prompt.

ScriptHost Add-in
ScriptHost hosts the VBScript engine, and allows you to run VBS macros from
VC6 in the VS7 IDE. You do not need to make any syntatical changes to your
code except for method/property calls that are not available in V7.

Web App Provider Add-in
This sample is the source code to the Web App Provider Add-in that allows
you to upload your website to Hosting Providers from the homepage.

GenerateHTML Add-in
The GenerateHTML Add-in takes the current selection in the texteditor and
generates colorized HTML with the proper formatting

Unsupported Tools
This archive contains unsupported tools to be used with the Visual Studio
Object model. The following items are currently available: ExtBrws.dll:To
use it, regsvr32 the dll, then load it from the VS or VBA Add-in Manager
dialog. Click the item on the Tools menu to bring up a dialog that displays
all the late bound objects available under DTE, DTE.Events, or
DTE.Properties. GenerateIcoData.exe:This is a program that will generate
data that can be entered into the registry settings for an Add-in that
displays Help About information. When generating the registry keys for Help
About information, an icon is needed for display. To generate the binary
data for the registry, you would create a binary value, and copy the hex
information into this value. This tool generates this information.

Simple Add-ins
The Add-in will display a Message Box as each member of IDTExtensibility2 is
called. It will also add a button to the Tools menu, which when clicked
displays a message. The Button that is added is done using the old style
Office CommandBars, rather than AddNamedCommand.

RegExplore Add-in
RegExplore is an Add-in that emulates the Registry Editor, but as a tool
window within the environment. Some of the object model functionality
demonstrated includes:
(13个)


Web Service Searching:

记得以前我在一篇有关Web Service的文章中写过,以后会有一个象现在门户网站一样的Web Service的门户网站,一个月前我发现了这家网站,当时它被记载在VisualstudioWire的Headlines中,目前它收集了许多方方面面的Web Serveice,当然从未来的眼光来看还是太少太少。更有趣的是你在VS.NET beta2中选择Project 菜单中的 Add Web Reference…,然后在弹出的对话框中直接输入http://www.salcentral.com/dotne.asp你发现这些Web Service直接就可以使用在你的应用程序中。
这家网站收集Web Service的速度还在飞涨,许多人都在Upload自己的Web Service。你可以去下面的网址了解具体的内容:
http://www.salcentral.com/dotnet/dnover.htm
http://www.salcentral.com/salnet/webserviceswsdl.asp


VS.NET 的命令行启动:

一个习惯了用命令行方式的人很难会去使用图形界面,VS98时就有许多大虾在用命令行方式编译他们那些用Notepad写的代码,到了VS.NET时代我想这些人还一样存在,而且MS内部也大有人在,所以命令行方式一定会保留下来,下面就是一些命令行参数,也许软件正是因为这些命令开关才显得神奇和有趣,而图形界面一直是傻瓜的象征(hahaha)

Available command line switches:
 
/build build the specified solution configuration
/project specifies the project to build instead of solution
 must specify /build to use /project
/projectconfig specifies project configuration to build
 must specify /project to use /projectconfig
/out write build output to specified file
/rebuild like /build but forces a clean first
/clean clean up build outputs
/deploy build the specified solution configuration and then deploy it
/run run the specified solution configuration
/runexit run the specified solution configuration and then terminate
/command executes the specified internal command line after startup
/mditabs use tabbed documents interface
/mdi use MDI interface
/fn use specified font name
/fs  use specified font size
/LCID use specified language ID
/noVSIP disables VSIP developers license key for VSIP testing
/safemode only default environment and services load for stability
/resetskippkgs allow VsPackages once flagged for loading failures to
 load again
 
Product-specific switches:
 
/debugexe Open the specified executable to be debugged. The
 remainder of the command line is passed to this
 executable as its arguments.
/useenv Use PATH, INCLUDE, LIBPATH, and LIB environment variables
 instead of IDE paths for VC++ builds.

2006年05月16日

1. 程序员写出自认为没有Bug的代码。
2. 软件测试,发现了20个Bug。
3. 程序员修改了10个Bug,并告诉测试组另外10个不是Bug。
4. 测试组发现其中5个改动根本无法工作,同时又发现了15个新Bug。
5. 重复3次步骤3和步骤4。
6. 鉴于市场方面的压力,为了配合当初制定的过分乐观的发布时间表,产品终于上市了。
7. 用户发现了137个新Bug。
8. 已经领了项目奖金的程序员不知跑到哪里去了。
9. 新组建的项目组修正了差不多全部137个Bug,但又发现了456个新Bug。
10. 最初那个程序员从斐济给饱受拖欠工资之苦的测试组寄来了一张明信片。整个测试组集体辞职。
11. 公司被竞争对手恶意收购。收购时,软件的最终版本包含783个Bug。
12. 新CEO走马上任。公司雇了一名新程序员重写该软件。
13. 程序员写出自认为没有Bug的代码。 

2006年05月09日
从ADO迁移到ADO.NET
        ★★★作者:佚名 来源:syku.net转载 阅读次数: 16 发布时间:2006-5-7

近几年来,ADO已经成为在基于Windows 的应用程序中执行数据访问的领先、优选的方法。今天,已经有大量的ADO应用程序付诸应用,许多开发人员已经对ADO开发非常熟练。随着Microsoft .NET Framework的引入,ADO.NETADO的演进版本粉墨登场了。虽然ADOADO.NET之间存在很多相似性,但是它们的运行方式和基础却大不相同。为了帮助读者平滑地迁移到ADO.NET,我将在本文中讲述如何用ADO.NET完成一些常见的任务。我会提出一些关于数据访问的情况进行探讨,说明如何利用ADO予以解决,并对比说明如何在用C#开发的ASP.NET应用程序通过ADO.NET进行解决。我将以连接数据源的相似操作为论述的切入点,然后,我们会看到ADORecordset对象在ADO.NET中如何演变为许多截然不同、各有侧重的对象和方法。最后,我将探讨流水游标(firehose cursor),它从行集中返回一个值并使用XML

 

ADO的演变

 

一些传统的ADO功能(例如,建立与数据源的连接)在各版本之间改变不大,而其他一些功能(例如,表示断开的行集、将行集保持为XML以及将其转换为分层次的行集)则发生了很大的变化。发生这种显著变化的原因之一在于,XML和数据构形(data-shaping)功能都是后来加入到ADO中的;而对于ADO.NET,它们在一开始就被纳入到设计中。与比较老的数据访问工具(例如DAORDO)相比,传统的ADO算是非常轻量级的。ADO之所以在VisualBasic 6.0 N层开发中如此流行,原因在于它简单、易于浏览的对象模型特性。ADOConnectionCommand对象可以相对容易地转换为ADO.NETConnectionCommand对象,但是ADORecordset对象的功能却要转换成ADO.NET中的许多不同的对象和方法。ADO在某种程度上依然是一种强大而流行的数据访问工具,其原因在于它的XML功能和管理断开行集的能力。ADO Recordset可以通过将其CursorLocation属性设置为adUseClient,将其CursorType属性设置为ADOpenStatic,将其LockType属性设置为adLockBatchOptimistic从而与数据源和Connection对象断开。然后,在Recordset打开并加载之后,可以通过将其ActiveConnection属性设置为Nothing,从而实现断开:

 

‘—Disconnecting an ADO Recordset

oRs.CursorLocation=adUseClient

oRs.CursorType=ADOpenStatic

oRs.LockType = adLockBatchOptimistic ‘- Or use adLockReADOnly

oRS.Open

Set oRS.ActiveConnection = Nothing

 

XML功能并不是在一开始就集成到ADO中的,而是随着XML越来越流行,它的功能才逐步被加入到ADO较新的版本中。ADO Recordset对象的Save方法能够接收记录集的行和列,并将它们转换为预定义的XML架构,然后将其持久存留到文件或者流之中。XML架构并不是非常的灵活,但它是从ADO行集保留XML的首次真正的尝试,并且它为开发人员简要地指明了开发的方向。只要文件使用与其所期望的架构所相同的XML架构,记录集还可以从XML文件中加载。下面的代码说明了ADO如何将记录集保存到一个XML文件中:

 

.Save "c:\MyRowSet.xml", adPersistXML

 

行集可以保存到一个流中,例如来自ASP页的Response对象的输出。举例而言,一些ASP代码能够马上响应请求,检索一个行集,并将该行集流转化为Response对象,再将其返回给客户端。下面的代码说明了如何将一个记录集的内容保存到Stream对象中。此外,Save方法的第一个参数可以用Response对象代替,这样就能将XML行集流转化到调用方浏览器:

 

DimoStmAsADODB.Stream

SetoStm=NewADODB.Stream

oRs.Save oStm,adPersistXML

 

传统的ADOASP也能够进行流转化。当然,在.NET中也可以使用Web服务实现,而且针对于任何使用传统的ADOASP轻易构建的解决方案,还提供了更多的功能。事实上,Web服务是设计用来处理数据请求并将数据以XML形式通过HTTP传回的一种技术。虽然ADO可以流转化为XML,但这是后来才在设计中添加的。ADO.NET DataSet对象有WriteXmlWriteXmlSchema方法,允许将它的内容导出到一个XML文件或者是流中。因此,虽然对ADO RecordsetSave方法已经进行了重新设计,从而能够将内容导出为XML;但是ADO.NETDataSet则从一开始就具备这一功能。ADO.NET不但能够将DataSet保留为XML,而且可以从XML加载一个DataSet;此外,它还利用其类似XML的结构在其他方面展示出更多的优点。例如,因为DataSet在本质上是表示为XML的,所以它可以在物理层和逻辑层之间轻易地传递。这就是说,XML可以通过HTTP跨安全网络进行传递,这是因为它采用了基于文本的XML。由于构建于XML之上,ADO.NET可以在断开情况下工作。传统的ADORecordset根据对一些属性的设置(例如,CursorType=ADOpenStaticCursorLocation=adUseClient)可以是连接的也可以是断开的,而ADO.NET则有专门的RowSet对象,可以连接数据源(DataReader)或者断开(DataSet)

 

连接

 

对于创建与之连接的过程来说,传统的ADOADO.NET非常相似。基本上,您都要声明连接对象,将其实例化,设置它的连接字符串并打开它,请参考图1。第一个示例说明如何使用ASPADO打开连接,第二个示例则说明如何使用ASP.NETADO.NET打开连接。

 

VBScript中,传统的ASPADO

dimoCn

dimsCn

setoCn=Server.CreateObject("ADODB.Connection")

sCn="Provider=SQLOLEDB;DataSource=(local);"&_

"InitialCatalog=Northwind;IntegratedSecurity=SSPI"

oCn.OpensCn

 

C#中,ASP.NETADO.NET

String sCn = "Provider=SQLOLEDB; DataSource=(local); Initial"+

"Catalog=Northwind;Integrated Security=SSPI";

SqlConnection oCn=newSqlConnection(sCn);

oCn.Open();

 

ADOADO.NET创建连接时的主要区别是:ADO与所有类型数据源的全部连接都使用一个Connection对象;而ADO.NET则有不同的对象代表与不同数据源的连接。例如,ADO.NET有一个名为System.Data.SqlClient的命名空间,容纳了所有专门用于SQL ServerADO.NET对象,包括SqlConnection对象。SqlConnection对象是专门构建用来与SQLServer数据库进行通信的,因此它是与SQLServer交互最快也是功能最丰富的方式。还有一个更一般性的命名空间名为System.Data.OleDb,它可以与任何OLEDB兼容的数据源进行通信。在ADO.NET中,可以创建多个data provider命名空间,专门与各个特定的数据源连接,这样能够使访问更加快速、有效,而且每个命名空间都能充分利用其目标data provider的功能。如果应用程序必须迅速地改变data provider的类型,或者主要使用一种没有特定ADO.NET连接对象的provider,它可以使用OleDbConnection

 

RecordsetDataReader

ADORecordset,其行为会根据属性设置情况的不同而异;而在ADO.NET中,已经分成许多不同的对象和方法。这样,ADO.NET的对象能够更有效地工作,因为它的对象可以专门从事最擅长的工作而用不着努力成为“万金油”。ADORecordset既可以是连接行集,也可以是断开行集。它能够作为只进、只读(流水)游标,也可以允许在行集中位置后移、前移和居中。ADO Recordset允许直接更改数据库中的数据,并且允许数据的更改批量地保存和发送到数据库。关键在于,ADORecordset是身兼数职的;ADO.NET则将这些功能分散到多个对象,而这些对象都进行了优化,能够完成特定的任务。这样,ADO Recordset的功能就分摊到DataSet对象、DataReader对象以及DataAdapterCommand对象的一部分。只进、只读游标要要与数据源保持持续连接。ADO.NET有一个DataReader对象,在打开时总是连接的。它是针对于各种data provider(例如SQLServerOracle和更一般的OLEDB provider)而专门编写的。因此,SqlDataReader对象可与SQLServer数据库连接,并作为流水游标在需要时循环遍历大量记录。SqlDataReader提供了对查询结果的快速只进访问方式,它从数据库的查询结果中检索到一条记录,并保持连接打开,从而能够获取接下来的下一个记录。ADO.NETDataReader绝对有效率,因为它不支持ADORecordset的所有功能。它只是一个轻型的流水游标机。图2中的示例说明了如何用传统的ADOADO.NET实现只进游标。请注意,在ADO.NET中,DataReader对象的Read方法能自动移动位置到下一个记录。这样就解决了开发人员在使用传统的ADO时由于遗漏ADORecordsetMoveNext方法而遇到的常见的死循环问题。

 

ASPADO

oRs.ActiveConnection = oCn

oRs.LockType=adLockReadOnly

oRs.CursorType=adOpenForwardOnly

oRs.CursorLocation =adUseServer

oRs.Open sSQL

Do While Not oRs.EOF

Response.WriteoRs("CompanyName") &"

"

oRs.MoveNext

Loop

 

C#中,ASP.NETADO.NET

SqlDataReaderoDr=oCmd.ExecuteReader();

while(oDr.Read())

{

Response.Write(oDr[0]+"

");

}

 

ADOADO.NET中的另一个区别是只进游标的填充方式。在ADO中,所有行集,无论只进与否,都包含在Recordset对象内,该对象通过Recordset.Open方法、ConnectionCommandExecute方法打开。而在ADO.NET中,构建了一个专门的方法用来检索只进数据到DataReader,即Command对象的ExecuteReader方法。这个方法通知Command对象为data provider专门创建一个DataReader对象,从而能够以优化的只进方式处理结果,如前面的示例所示。Command对象还有一个称为ExecuteXmlReader的方法,它通知Command对象将查询结果设置为由XmlReader对象进行处理。XmlReader对象可以用来转换和处理XML查询结果,如下面的ASP.NET示例所示:

 

oCn.Open();

SqlCommand oCmd = new SqlCommand("SELECT * FROM Orders FOR XML AUTO",oCn);

oCmd.CommandType = CommandType.Text;

XmlReader oXR = oCmd.ExecuteXmlReader();

while(oXR.Read())

{

Response.Write(oXR.ReADOuterXml());

}

 

这个示例说明如何将XML发送给调用方浏览器。但是,XML也能够作为一个整体进行收集和流转化为Response

 

单值命令

 

大多数应用程序有时候都需要从数据库查询中检索单值。在传统的ADO中,实现这一点的标准方式是创建一条SQL语句,并打开包含查询结果的记录集。由于在查询的结果中只有一行和一列,似乎这样获取一个值有些小题大做了。下面的示例从一个查询中检索一个值,该示例获取了Orders表的行数:

 

‘– ASP and ADO

Set oRs = Server.CreateObject("ADODB.Recordset")

oRs.ActiveConnection=oCn

oRs.Open "SELECT COUNT(*) As iRowCountFROMOrders"

iCount=oRs.Fields("iRowCount").Value

 

ADO.NET引入了一种从查询的结果中获取单值的新方式,可以用于预计只返回一行和一列的场合。ADO.NETCommand对象有一个ExecuteScalar方法,它从相关的查询中返回第一行和第一列的值。因为不用创建行集、查找值并关闭行集,所以这样所产生的系统开销非常小。ExecuteScalar方法已经针对需要检索单值的特定场合进行了优化。下例实现的功能与前例相同,只不过使用的是ASP.NETADO.NET以及ExecuteScalar方法:

 

string sSql = "SELECTCOUNT(*) As iRowCountFROMOrders";

SqlCommandoCmd=newSqlCommand(sSql,oCn);

oCmd.CommandType = CommandType.Text;

int iCount = (int)oCmd.ExecuteScalar();

 

检索单值的另一种方式是使用存储过程的输出参数。例如,这一技术也可以从一个单行中检索许多值。它在ADOADO.NET中都适用,虽然ADO.NET已经扩展了输出参数的功能。在ADO.NET中为了从Command对象获取输出变量的值,应该使用ExecuteNonQuery方法执行查询。该方法能够通知ADO.NET,查询不会返回一个行集,因此避免了DataSet或者DataReader的系统开销:

 

oCmd.ExecuteNonQuery();

oCmd.UpdatedRowSource=UpdateRowSource.OutputParameters;

intiOrderID=(int)oCmd.Parameters["@OrderID"].Value;

 

此段代码将UpdatedRowSource属性设置为指向输出参数(假定它们已经进行了设置),然后就可以检索输出值了。在传统的ADO中这是在Connection对象的Execute方法中使用晦涩难懂的参数实现的,而我们可以看到,在ADO.NET中实现这一点已经非常简单了。当然,ADO.NET还有一个为返回标准行集进行了优化的方法Command对象的Execute方法。在传统的ADO中,Recordset对象能够设置UPDATEINSERTDELETE语句,这些语句是为了使Recordset能够将对自己的任何更改应用于基础数据库所必需的。虽然这一功能非常方便,但因为必须要返回给数据库以决定如何实现,所以,它也增加了系统开销。ADO.NET对象通过CommandBuilder对象也可以实现这一点;但同样也存在着系统开销的问题。在大多数开发场景中,要求使用的确切SELECTINSERTUPDATEDELETE语句在设计时是已知的。在传统的ADO中,没有什么简单的办法将操作查询与Recordset相关联从而来使用它们。而在ADO.NET中,DataAdapter有四个不同的Command对象与其相关联,可以表示每个操作查询以及SELECT语句。这使DataAdapter能够协助我们用查询的结果填充一个DataSet,还能提前通知DataAdapter对数据库发出操作查询。虽然这需要在设计时投入更多的编码工作,但是代码的增加换来的是性能上的改善(就更不用说代码易于理解所带来的维护上的轻松了)。

  ADO.NET是.Net FrameWork SDK中用以操作数据库的类库的总称。而DataSet类则是ADO.NET中最核心的成员之一,也是各种开发基于.Net平台程序语言开发数据库应用程序最常接触的类。之所以DataSet类在ADO.NET中具有特殊的地位,是因为DataSet在ADO.NET实现从数据库抽取数据中起到关键作用,在从数据库完成数据抽取后,DataSet就是数据的存放地,它是各种数据源中的数据在计算机内存中映射成的缓存,所以有时说DataSet可以看成是一个数据容器。同时它在客户端实现读取、更新数据库等过程中起到了中间部件的作用(DataReader只能检索数据库中的数据)。

  各种.Net平台开发语言开发数据库应用程序,一般并不直接对数据库操作(直接在程序中调用存储过程等除外),而是先完成数据连接和通过数据适配器填充DataSet对象,然后客户端再通过读取DataSet来获得需要的数据,同样更新数据库中数据,也是首先更新DataSet,然后再通过DataSet来更新数据库中对应的数据的。可见了解、掌握ADO.NET,首先必须了解、掌握DataSet。DataSet主要有三个特性:

  1. 独立性。DataSet独立于各种数据源。微软公司在推出DataSet时就考虑到各种数据源的多样性、复杂性。在.Net中,无论什么类型数据源,它都会提供一致的关系编程模型,而这就是DataSet。

  2. 离线(断开)和连接。DataSet既可以以离线方式,也可以以实时连接来操作数据库中的数据。这一点有点像ADO中的RecordSet。

  3. DataSet对象是一个可以用XML形式表示的数据视图,是一种数据关系视图。

  一.DataSet对象的结构模型及和RecordSet的比较

  虽说ADO.NET是 ADO在.Net平台下得后继版本,但二者的区别是很大的。突出表现在ADO中的RecordSet对象和ADO.NET中的DataSet对象。RecordSet其实也是非常灵活的一个对象,微软公司推出它也是煞费苦心,如:RecordSet可以离线操作数据库,性能优良,效率较高等等这些都让当时的程序员为之一振。RecordSet虽然已经很复杂,但DataSet却比RecordSet复杂的多,我们知道每一DataSet往往是一个或多个DataTable 对象的集合,这些对象由数据行和数据列以及主键、外键、约束和有关DataTable对象中数据的关系信息组成。而RecordSet只能存放单张数据表,虽然这张数据表可以由几张数据表JOIN生成。所以有些时候说,RecordSet更类似于DataSet中的DataTable。DataSet对象的结构模型如图01所示:



图01:DataSet对象的结构模型图


  通过图01可见在DataSet对象结构还是非常复杂的,在DataSet对象的下一层中是DataTableCollection对象、DataRelationCollection对象和ExtendedProperties对象。上文已经说过,每一个DataSet对象是由若干个DataTable对象组成。DataTableCollection就是管理DataSet中的所有DataTable对象。表示DataSet中两个DataTable对象之间的父/子关系是DataRelation对象。它使一个DataTable 中的行与另一个DataTable中的行相关联。这种关联类似于关系数据库中数据表之间的主键列和外键列之间的关联。DataRelationCollection对象就是管理DataSet中所有DataTable之间的DataRelation关系的。在DataSet中DataSet、DataTable和DataColumn都具有ExtendedProperties属性。ExtendedProperties其实是一个属性集(PropertyCollection),用以存放各种自定义数据,如生成数据集的SELECT语句等。

  二.使用DataSet:

  DataSet其实就是数据集,上文已经说过DataSet是把数据库中的数据映射到内存缓存中的所构成的数据容器,对于任何数据源,它都提供一致的关系编程模型。在DataSet中既定义了数据表的约束关系以及数据表之间的关系,还可以对数据表中的数据进行排序等。DataSet使用方法一般有三种:

  1. 把数据库中的数据通过DataAdapter对象填充DataSet。

  2. 通过DataAdapter对象操作DataSet实现更新数据库。

  3. 把XML数据流或文本加载到DataSet。

  下面就来详细探讨以上DataSet使用方法的具体实现,使用语言是C#。

  1. 把数据库中的数据通过DataAdapter对象填充DataSet:

  掌握DataSet使用方法必须掌握ADO.NET另外一个核心常用成员–数据提供者(Data Provider)。数据提供者(也称为托管提供者Managed Provider)是一个类集合,在.Net FrameWork SDK 1.0中数据提供者分为二种:The SQL Server .NET Data Provider和The OLE DB .NET Data Provider。而到了.Net FrameWork SDK 1.1时,ADO.NET中又增加了The ODBC .NET Data Provider和 The Oracle .NET Data Provider二个数据提供者。The SQL Server .NET Data Provider的操作数据库对象只限于Sql Server 7.0及以上版本,Oracle .NET Data Provider的操作数据库对象只限于Oracle 8.1.7及以上版本。而The OLE DB .NET Data Provider和The ODBC .NET Data Provider可操作的数据库类型就相对多了许多,只要它们在本地分别提供Ole Db提供程序和ODBC提供程序。

  在这些数据提供者中都有一个DataAdapter类,如:OLE DB .NET Framework 数据提供者中是 OleDbDataAdapter类,The SQL Server .NET Framework 数据提供者中是SqlDataAdapter类,The ODBC .NET Framework 数据提供者中是OdbcDataAdapter类。通过这些DataAdapter就能够实现从数据库中检索数据并填充 DataSet 中的表。

  DataAdapter填充DataSet的过程分为二步:首先通过DataAdapter的SelectCommand属性从数据库中检索出需要的数据。SelectCommand其实是一个Command对象。然后再通过DataAdapter的Fill方法把检索来的数据填充 DataSet。代码清单01就是以Microsoft SQL Server 中的Northwind数据库为对象,C#使用The SQL Server .NET Data Provider中的SqlDataAdapter填充DataSet的具体实现方法:

  代码清单01:


SqlConnection sqlConnection1 = new SqlConnection ( "Data Source=localhost ;Integrated Security=SSPI ;Initial Catalog=Northwind" ) ;
//创建数据连接
SqlCommand selectCMD = new SqlCommand ( "SELECT CustomerID , CompanyName FROM Customers" , sqlConnection1 ) ;
//创建并初始化SqlCommand对象
SqlDataAdapter sqlDataAdapter1 = new SqlDataAdapter ( ) ;
custDA.SelectCommand = selectCMD ;
sqlConnection.Open ( ) ;
//创建SqlDataAdapter对象,并根据SelectCommand属性检索数据
DataSet dsDataSet1 = new DataSet ( ) ;
sqlDataAdapter1.Fill ( dsDataSet1 , "Customers" ) ;
//使用SqlDataAdapter的Fill方法填充DataSet
sqlConnection.Close ( ) ;
//关闭数据连接


  对于其他数据提供者的DataAdapter,具体的实现检索数据库中的数据并填充DataSet的实现方法类似于以上方法。

  2. 通过DataAdapter对象操作DataSet实现更新数据库:

  DataAdapter是通过其Update方法实现以DataSet中数据来更新数据库的。当DataSet实例中包含数据发生更改后,此时调用Update方法,DataAdapter 将分析已作出的更改并执行相应的命令(INSERT、UPDATE 或 DELETE),并以此命令来更新数据库中的数据。如果DataSet中的DataTable是映射到单个数据库表或从单个数据库表生成,则可以利用 CommandBuilder 对象自动生成 DataAdapter 的 DeleteCommand、InsertCommand 和 UpdateCommand。使用DataAdapter对象操作DataSet实现更新数据库具体的实现方法,只需把下面的代码清单02添加到代码清单01之后,二者合并即可实现删除Customers数据表中第一行数据:

  代码清单02: 


SqlCommandBuilder sqlCommandBuilder1 = new SqlCommandBuilder ( sqlDataAdapter1 ) ;
//以sqlDataAdapter1为参数来初始化SqlCommandBuilder实例
dsDataSet1.Tables["Customers"].Rows[0].Delete ( ) ;
//删除DataSet中删除数据表Customers中第一行数据
sqlDataAdapter1.Update ( dsDataSet1 ,"Customers" ) ;
//调用Update方法,以DataSet中的数据更新从数据库
dsDataSet1.Tables["Customers"].AcceptChanges ( ) ;


  由于不了解DataSet结构和与数据库关系,很多初学者往往只是更新了DataSet中的数据,就认为数据库中的数据也随之更新,所以当打开数据库浏览时发现并没有更新数据,都会比较疑惑,通过上面的介绍,疑惑应当能够消除了。

  3. XML和DataSet:

  DataSet中的数据可以从XML数据流或文档创建。并且.Net Framework可以控制加载XML数据流或文档中那些数据以及如何创建DataSet的关系结构。加载XML数据流和文档到DataSet中是可使用DataSet对象的ReadXml方法(注意:ReadXml来加载非常大的文件,则性能会有所下降)。ReadXml 方法将从文件、流或 XmlReader 中进行读取,并将 XML 的源以及可选的 XmlReadMode 参数用作参数。该ReadXml方法读取 XML 流或文档的内容并将数据加载到 DataSet 中。根据所指定的XmlReadMode和关系架构是否已存在,它还将创建DataSet的关系架构。

  三.DataSet和数据绑定(DataBinding)

  数据绑定是数据绑定是绑定技术中使用最频繁,也是最为重要的技术,也可以说是各种.Net开发语言开发数据库应用程序最需要掌握的基本的知识之一。数据绑定之所以很重要,是因为在.Net FrameWork SDK中并没有提供数据库开发的相关组件,即如:DbTextBox、DbLabel等用于数据库开发的常用组件在.Net FrameWork SDK中都没有。而数据绑定技术则能够把TextBox组件"改造"成DbTextBox组件,把Label组件"改造"成DbLabel组件等等。所有这些都与DataSet有直接关系。

  数据绑定分成二类:简单型数据绑定和复杂型数据绑定。适用于简单型数据绑定组件一般有Lable、TextBox等,适用于复杂性数据绑定的组件一般有DataGrid、ListBox、ComboBox等。其实简单型数据绑定和复杂性数据绑定并没有明确的区分,只是在组件进行数据绑定时,一些结构复杂一点的组件在数据绑定时操作步骤相近,而另外一些结构简单一点的组件在数据绑定时也比较类似。于是也就产生了二个类别。以下就结合TextBox组件和DataGrid组件分别探讨DataSet在实现简单型数据绑定和复杂性数据绑定作用和具体实现方法。

  1. 简单型数据绑定:

  简单型数据绑定一般使用这些组件中的DataBindings属性的Add方法把DataSet中某一个DataTable中的某一行和组件的某个属性绑定起来,从而达到显示数据的效果。TextBox组件的数据绑定具体实现方法是在代码清单01后,再添加代码清单03中的代码,代码清单03中的代码是把DataSet中的Customers 数据表中的"CustomerID"的数据和TextBox的Text属性绑定起来,这样DbTextBox就产生了。其他适用于简单型数据绑定组件数据绑定的方法类似与此操作:

  代码清单03:


textBox1.DataBindings.Add ( "Text" , dsDataSet1, " Customers. CustomerID " ) ;


  2. 复杂性数据绑定:

  复杂性数据绑定一般是设定组件的DataSource属性和DisplayMember属性来完成数据绑定的。DataSource属性值一般设定为要绑定的DataSet,DisplayMember属性值一般设定为要绑定的数据表或数据表中的某一列。DataGrid组件的数据绑定的一般实现方法是在代码清单01后,再添加代码清单04中的代码,代码清单04的功能是把DataSet中的Customers 数据表和DataGrid绑定起来。其他适用于复杂性数据绑定的组件实现数据绑定的方法类似此操作:

  代码清单04:


dataGrid1.DataSource = dsDataSet1 ;
dataGrid1.DataMember = " Customers " ;


  四.总结

  DataSet类是ADO.NET中一个非常重要的核心成员,它是数据库中的数据在本地计算机中映射成的缓存。对DataSet的任何操作,都是在计算机缓存中完成的。理解这一点是掌握DataSet的第一步。DataSet虽然结构复杂。但只要区分DataSet对象中各个组成部件及其相互关系,掌握也不算太困难。本文覆盖了DataSet的特性、结构、以及具体的使用方法等等,相信这些对您掌握这个ADO.NET中核心成员是有所帮助

2006年05月08日

利用 ADO.NET 连接到 Informix

本文将深入研究使用 ADO.NET 和 Informix® Dynamic Server 的一些细节,而不只是基本的连接字符串。本文还将介绍存储过程和参数化的 SQL,然后将展示一个用于生成您自己的强类型数据集的模型(使用代码)。 V 0wS  
简介 QHL|IMl  
利用其庞大的 .NET 框架,Microsoft® 引入了一种叫做 ADO.NET 的新的数据访问技术。在本文中,我们将分析如何使用 Informix 的 ADO.NET 驱动程序,该程序包含在 IBM Client SDK 版本 2.90 中。所包含的示例代码是用 C# 编写的。
WKb cK  
V,>sO#It!8  
EW70! Ss  
,0;H= |ZKM  
回页首
,L ),#vmYe
 
4XXy|Qnj}Z  
背景介绍 XC{2;wiQZt  
在使用 ADO.NET 驱动程序之前,应该确保该驱动程序已安装并能正确运行。该驱动程序的当前版本可以使用 Informix Client Software Developer’s Kit (SDK) 2.90 来安装。与以前的 2.81 版本不同,此 SDK 版本包括默认的 ADO.NET 驱动程序。SDK 的安装程序也会警告您有关事项。它并不真正在您的计算机上查找 .NET 框架的安装,只是警告您必须在安装 SDK 之前安装好 .NET 框架。如果您已经安装了 2.81 SDK,那么最好先卸载它。这两个版本无法共存。还要认识到的一点是,在将 2.90 ADO.NET 驱动程序添加到 Visual Studio Projects 中时,它会不正确地报告它自己是版本 2.81。
cT Y1V  
2.9 版本是在 2.81 版本之上的一次重要升级。它包括一个新的 IfxDataAdapter 向导、IPv6 支持和一些用于 Informix 数据类型(IfxDateTime、IfxDecimal、IfxBlob 和 IfxClob)的新类。该文档更为完善,内容总量是以前的两倍。
FE}=D i  
要点:IBM Informix ADO.NET 驱动程序并不仅仅包含在安装目录下的 /bin 目录下的 IBM.Data.Informix.dll 文件中。显然,它使用了由 SDK 安装的其他客户端代码。这意味着您必须在所有将使用 ADO.NET 驱动程序的机器上安装 Informix Client SDK。您不能只在您的发行版中包括 IBM.Data.Informix.dll。这对一些应用程序而言可能是一个严重的限制。您还需要仔细检查 SDK 安装程序 (SetNet32),以定义 Informix 数据源。 -Xb-UKE  
在将 ADO.NET 驱动程序用于连接之前,还必须运行一个叫做 cdotnet.sql 的存储过程。这个存储过程位于 SDK 安装的 /etc 目录中。这类似于设置 OLEDB 驱动程序的过程,尽管这个过程更短一些。这个过程记录在 User’s Guide 中。(请参阅下面的
&vz>;|F]DF  
up<,D$w(  
参考资料
/},’*AedG  
[D&JWx 1  
部分。)
zxe:qer^  
在完成安装之后,检查一下驱动程序,确保建立了连接。要在 Visual Studio 项目中使用 ADO.NET 驱动程序,则必须确保已将一个引用添加到客户端 SDK 安装的 /bin 目录中找到的 IBM.Data.Informix.dll 中。正确的 using 语句是:using IBM.Data.Informix。以下是一个演示如何获得到数据库的连接的简单方法:
77sNm}q  
清单 1. 到 Informix 数据库的连接 C ':'  
public void MakeConnection() {
Z(d_7*J('  
  string ConnectionString = "Host=" + HOST + "; " +
LLH@1oa  
  "Service=" + SERVICENUM + "; " +
 JXvU,U <  
  "Server=" + SERVER + "; " +
L]F1w/JS-  
  "Database=" + DATABASE + "; " +
%zvI#I;i  
  "User id=" + USER + "; " +
KkDu’ %)  
  "Password=" + PASSWORD + "; ";
;;FcwE%L  
  //Can add other DB parameters here like DELIMIDENT, DB_LOCALE etc
[\>4.i\^w  
  //Full list in Client SDK's .Net Provider Reference Guide p 3:13
5+ SIp  
  IfxConnection conn = new IfxConnection();
BI"h}=;  
  conn.ConnectionString = ConnectionString;
oEixlCL  
  try {
DJNw9)  
    conn.Open();
X6sHQ>r|V  
    Console.WriteLine("Made connection!");
BZG1m  C  
    Console.ReadLine();
je9 }~_  
  } catch (IfxException ex) {
ZV]p@]Q’,  
    Console.WriteLine("Problem with connection attempt: "
mkgyZ7 w  
                + ex.Message);
*X1DK){Q  
  }
,=]`5  
}
{n])SKbf  
示例代码中包括一个用于此功能的 BasicConnection 类。如您所见,ConnectionString 只是一个用于连接的分号分隔的参数列表。Open() 方法打开了到数据库的连接,如果连接失败,则抛出一个 IfxException。IfxException.Message 属性通常提供关于失败原因的合理数量的详细信息。
zlwvp:b  
_\Ml)  
[-F:G  
#Wc1]‘hAt  
回页首
%{kBkK{m9
 
-SsQH{<%  
基本命令 Q’TH(  
一旦建立了连接,就可以开始对数据库执行命令。要做到这一点,需要使用 IfxCommand 对象。IfxCommand 的构造函数接收一个字符串(SQL 命令文本)和一个 IfxConnection 作为参数。IfxCommand 对象有一系列的 Execute 方法,以便对数据库执行命令。要清除连接,可以使用 IfxConnection.Close() 方法。以下是执行某个不返回结果集的简单命令的例子。该命令可能是 insert、update 或 delete。
WQ5<}R"E  
清单 2. 执行 insert、update 或 delete 命令 =Py* eh)h  
IfxCommand cmd;
U1&Og|AP  
cmd = new IfxCommand("insert into test values (1, 2, ‘ABC’)",conn);
0n7BoY’^c  
cmd.CommandTimeout = 200; //seconds to wait for command to finish |)e:B  
try {
dDDoAu-?X  
  int rows = cmd.ExecuteNonQuery();
B)JQ{7  
}
 |*2’  
catch (IfxException ex) {
(K-j7!K^  
  Console.WriteLine("Error "+ex.Message);
BkN=8nk8  
}
sc=&DA$  
ExecuteNonQuery 以整数形式返回受命令影响的行数。您还可以构建参数化语句和查询,后面部分将对它们进行研究。注意 IfxCommand 的 CommandTimeout 属性。默认超时时间是 30 秒,尽管在文档中没有对此进行说明。除非更改此属性,否则运行 30 秒后,命令就会超时,并且将抛出一个异常。
"9b@X=  
下一个例子是执行一条 select 语句,并处理由数据库服务器返回的结果集。对于快速的、只向前通过结果的游标,可以使用由 ExecuteReader 方法返回的 IfxDataReader。不过,每个 IfxConnection 只可以有一个打开的 IfxDataReader。(这是一条 ADO.NET 限制,不是 Informix ADO.NET 驱动程序的特定限制。)
Ck4>lteiFf  
清单 3. 迭代通过 IfxDataReader L3y3b/)X  
IfxCommand cmd = new IfxCommand("select * from test",bconn.conn);
 cqX[nZ  
try {
!z%,B3|  
  IfxDataReader dr = cmd.ExecuteReader();
D*cDCm{l  
  while (dr.Read()) {
:${Fa >=,]  
    int a = dr.GetInt32(0);
4_P YJE  
    int b = Convert.ToInt32(dr["b"]);
 @q4eD  
    string c = (String)dr[2];
f/\#q  
  }
Qm+j";6 2  
  dr.Close();
dV ~DsA,  
}
go5K+ZT-G  
catch (IfxException ex) {
 Gs *-;*  
  Console.WriteLine("Error "+ex.Message);
" 5pygQ&  
}
Aj’ Oto]>  
每一列都被作为一般的 Object 类型进行检索。正如代码所演示的,存在一些将列 Objects 转换为正确数据类型的方法。您可以使用 IfxDataReader 的 GetXxx 方法。对于每种数据类型,几乎都有相应的方法。GetXxx 方法将列数目作为一个参数。可以使用 IfxDataReader 的索引,通过列的名称来访问列。如果可能的话,.NET 框架的 Convert 函数可以将这些 Objects 转换为正确的类型。最后,可以根据列编号为这些列建立索引,并直接强制转换结果(对于某些类型)。
&yI7KNI\w  
下一个例子将展示如何调用需要一个参数值的存储过程。
k(DQ65  
清单 4. 执行带有一个参数的存储过程 96c&(y  
IfxCommand cmd = new IfxCommand("test_proc",conn);
d;BY,\C8  
cmd.CommandType = CommandType.StoredProcedure; //from System.Data
J’!Q{MJC  
cmd.Parameters.Add("in_parameter",2); //many ways to create these
$o,>3GqQ_  
try {
Y5.p,L m  
  cmd.ExecuteScalar();
4E0r &K  
}
<Q 6^  
catch (IfxException ifxe) {
|UZQ+\>W}  
  Console.WriteLine("Error "+ifxe.Message);
ka&d0r0C  
}
MuggGYOx  
对于此 IfxCommand,必须将 CommandType 设置为来自 System.Data 中的 CommandType 枚举的 StoredProcedure 值。为了创建参数,可以使用 IfxCommand 的 Parameters.Add 方法。IfxCommands.Parameters 是一个集合,因此您可以添加您所需数量的参数。您可以使用任意 IfxParameter() 构造函数来创建参数,或者可以像上面这样简化参数的创建。不过要注意的是,每个 IfxParameter 都与一个特定的 IfxCommand 相关。您不能先创建 IfxParameters,然后在多个 IfxCommand 对象中使用它们。ExecuteScalar() 方法现在只返回 1。这一示例没有从存储过程返回任何东西。
s5#5 0{5Z  
要构建一个不执行存储过程的参数化 SQL 语句,需要将问号作为占位符插入 CommandText 中。例如:
SO|YZ"rZ  
清单 5. 参数化查询 ?^-Kt@hS  
IfxCommand insCmd = new IfxCommand("insert into clientstest "
3NpvP(^  
  + "(clientcode, clientacctname, primarycontact, primaddrcode, "
tY`5>@5  
  + "initialamt,createdate) values (0,?,?,?,?,TODAY)",conn);
z4j L|s  
按照 IfxParameter 对象在命令文本中的顺序,将这些对象添加到 IfxCommand 的 Parameters 集合中。在下面的扩展示例中的最终强类型化 DataSets 中,将进一步演示此技术。
^KbtwF’H  
@|WHY  
wm/??a&b  
LU d<REK  
回页首
uM`aZbL
 
.D?(vzbQ  
强类型数据集 ^=Jx9+%  
ADO.NET 包括一个叫做 DataSet 的专用数据库对象。它是一个内存数据库。DataSet 由一个或多个(由一些 DataRow 对象的)DataTable 对象组成。DataTable 可以通过主键和外键相关联。可以对数据设置一些约束。DataSet 也与实际的数据存储断开连接,可以通过一个或多个 DataAdapter(每个 DataTable 一个)来填充它,然后在内存中保存这些数据和所有更改。稍后,DataAdapter 可以将这些更改提交回数据存储。
t_8+7}  
基本的 DataSet 不是强类型的。它不知道数据库的实际行和列是什么。因此编译器没有检查这些列名称。直到运行的时候,列名称中的任何错误才会显现出来。此外,当开发者记不清列名是 "itemcode" 还是 "itemid" 的时候,他会发现基本的 DataSet 在这方面毫无帮助。
>H? ,  
一个强类型的 DataSet 可以解决这些问题。而一个普通的 DataRow 却无法取代它,因为普通的 DataRow 只有一个(例如)作为 OrderDetailDataTable 一部分的 OrderDetailDataRow。您可以将这些列作为 OrderDetailDataRow 的实际属性 (row.ItemCode) 进行引用。用这种方式,可以提高 IntelliSense 的生产率。表名称和列名称在属性编辑器中也会变得有效,从而增强诸如数据绑定之类的设计人员级工具。
-P_K}!pfl  
那么,怎样构建这个提高生产率的强类型 DataSet 呢?要花费如此多的时间或精力来构建一个您还没有体验到任何净生产效率的东西吗?Informix ADO.NET 驱动程序可能没有其他一些驱动程序那么复杂。Microsoft 的 SQLDataAdapter(用于 SQL Server)包括一个 Generate DataSet 向导。而 IfxDataAdapter 没有这样的向导。不过,您可以构建一些工具来帮助您,也可以使用一些已在 .NET 框架中构建的工具。最后,您将拥有封装所有数据库交互的强类型 DataSet 的一个子代。
ZH6fR  
.NET 框架包括一个 XSD 编译器 (xsd.exe),它可以从某个经过特殊格式化的 .xsd 文件中生成一个强类型 DataSet。但是,谁想键入一串 XML 呢?幸运的是,DataSet 对象包括一个叫做 WriteXmlSchema() 的方法。此方法允许您使用非类型化的 DataSet 为强类型 DataSet 创建 XSD 文件。让我们来看一下如何做到这一点。以下是一个示例表:
1pnVSS}\r  
清单 6. Clientstest 表 jC5kWAe9  
CREATE TABLE clientstest (
J% };Op  
clientcode SERIAL not null,
Ca0=O~1  
clientacctname CHAR(60) not null,
/N*%d[   
primarycontact CHAR(30) not null,
?Jgnu! O  
primaddrcode CHAR(10),
&P^#51//a+  
createdate DATE,
U%Oo<0  
initialamt DECIMAL (18,0)
.Fa 85m  
);
xG ^J4h{_  
以下是用于此表的单表 DataSet :
f?!oKhID  
清单 7. 定义 DataSet {=)m\;  
DS = new DataSet("dsClients");
llhAU\5:Y  
//main table definition
SRDe<r4h  
DataTable mainTable = new DataTable("clients");
Bux_.D)I  
DataColumnCollection cols = mainTable.Columns;
1m>DfZ=J  
DataColumn column = cols.Add("clientcode",typeof(Int32));
q%@(-5  
column.AllowDBNull = false;
T 2$/?mrcl  
cols.Add("clientacctname",typeof(String)).MaxLength = 60;
8Hx-=d  
cols.Add("primarycontact",typeof(String)).MaxLength = 30;
d7TrEhf  
cols.Add("primaddrcode",typeof(String)).MaxLength = 10;
>,\lNd8U  
cols.Add("initialamt",typeof(Decimal));
@d$KaB&  
cols.Add("createdate",typeof(System.DateTime));
be!ENS'9jw  
//primary key
]>`NvWnu  
mainTable.PrimaryKey = new DataColumn[] {cols["clientcode"]};
8 0zZ  
//add table to DataSet
nNB0H.].q  
DS.Tables.Add(mainTable);
ZPbpQMD#  
//Write schema to file
k-Ry\zj Nt  
DS.WriteXmlSchema("dsClients.xsd");
.p hY=Q  
在这个定义中,可以在数据上设置类型和限制条件。还可以设置列名称。这些名称不必与数据库的列名称匹配。观察本文
^zREb*Q$n1  
3emN:bV  
下载
`Poy*$ z  
M@I5Y  
部分中的代码文件,以查看得到的 dsClients.xsd 文件。
lq%c}+  
为了使生成 XSD 文件(或者在发生更改后重新生成它)变得更容易,可以为这些 DataSet Builders 构建一个框架。(完成此任务所需的所有代码都包含在下面部分。)在想用该框架确定要构建哪些 Builders 时,可以使用反射来动态确定某一 Builders 是否是 DataSetBuilder。让我们从编写 IBuildable 接口开始。它定义了 DataSetBuilder 必须实现的属性和方法。
pJcW*h?a  
清单 8. IBuildable 接口 0Mny- %_u  
public interface IBuildable {
XdI3PKR  
  string FileName {get; set;}
LQnU[Gqk  
  string FilePath {get; set;}
344A&+Zye  
  Logger Log {get; set;}
+U^1NjTH  
  DataSet DS {get; set;}
c);"V&68  
  void BuildXSD();
zM"%oL  
  void CompileXSD(string outputDirectory);
&!MT!7LpJ  
}
>HxCgY%}  
清单 7 中的代码(DataSet 的定义)主要是 BuildXSD() 方法。创建一个称为 DataSetBuilder 的抽象父类。BuildXSD 是必须在每个具体后续类中重写的抽象方法。对于每个 DataSetBuilder 而言,CompileXSD 方法是相同的,因此它位于 DataSetBuilder 中。以下是来自此抽象类的 CompileXSD() 方法:
 ]tMY)-  
清单 9. CompileXSD() 方法 u` 7ZMJ7  
log.LogStatus("Compiling "+filename);
ESr:y9,0  
ProcessStartInfo processinfo = new ProcessStartInfo();
V@Nhi  
processinfo.FileName =
> fp.%o  
@"C:\Program Files\Microsoft Visual Studio .NET 2003\"
ZzZ7km<Z  
+@"SDK\v1.1\Bin\xsd.exe";
"v;]RoT.  
processinfo.Arguments = FilePath+FileName+" /dataset /namespace:"
S4FE{T7  
+ds.Namespace+" /out:"+outputDirectory;
eYDu3 uM  
processinfo.UseShellExecute = false;
f?76c7  
processinfo.RedirectStandardInput = true;
39?I`[K)  
processinfo.RedirectStandardOutput = true;
COW/Qa  
processinfo.RedirectStandardError = true;
Ui68qPD1cs  
processinfo.CreateNoWindow = true; //doesn't work
8Lbq{  
processinfo.WindowStyle = ProcessWindowStyle.Hidden; //doesn't work
9Sh/Of  
Process compiler = Process.Start(processinfo);
VrA`Ax'  
log.LogStatus("Output:\n"+compiler.StandardOutput.ReadToEnd());
%^iR1#W#  
log.LogStatus("Error:\n"+compiler.StandardError.ReadToEnd());
h`R9a=ya  
compiler.WaitForExit();
QP+je|7, 6  
此方法使用 Process 和 ProcessStartInfo 类来执行 XSD 编译器,这两个类来自 .NET 框架的 System.Diagnostics。此示例代码使用了来自 ObjectGuy 的、免费并且相关的 .NET Logging Framework(请参阅
|07W2ODi`;  
c\v Ij  
参考资料
G7=Cy>Eg%  
zdiLF|(i  
)。
$NN%EgL q  
因为所有 DataSetBuilder 类都要实现 IBuildable 接口,所以我们可以使用反射来浏览程序集,并从 DataSetBuilders 构建所有 DataSet 类。这正是 DataLibraryBuilder 类要做的事情。例如,ClientsBuilder 被编译到一个 dsClients 类中。
kjBNg^9'r  
生成的 dsClients 类是一个强类型 DataSet。从第 42 行的 ClientsBuilder 开始,我们现在几乎拥有 500 行强类型化代码。请查看这个生成的代码,它包含一个 clientsDataTable。clientsDataTable 拥有每个列的属性。还有一些如下所示方法:NewclientsRow()、IsinitialamtNull() 和 FindByclientcode(int clientcode)。在使用这个类时,这些方法非常有用。
F }e~vI1zt  
要将 Informix 数据库访问封装在强类型化 DataSet 中,需要继承 dsClients。这是您可以在应用程序中使用的 Clients 类。继承性提供了一些保护,防止在 DataSet 模式下发生更改。如果该模式发生更改,则可以重新生成 dsClients 类。Clients 类未被更改(尽管您可能也需要对此进行一些更改)。在 Clients 类中,可以为每个 DataTable 添加一个 IfxDataAdapter(在这种情况下,只能添加一个)。对于每个 IfxDataAdapter,需要定义 SQL 文本,并为 select、insert、update 和 delete 命令定义参数。然后可以重写 Fill 和 Update 方法,以初始化、填充和更新所有 IfxDataAdpaters。查看以下作为一个例子的 Insert 命令:
41ZY=!BcPO  
清单 10. IfxDataAdapter 的 InsertCommand UqO3K<hpL  
IfxCommand insCmd = new IfxCommand("insert into clientstest "
TJfDCt3ST  
+"(clientcode, clientacctname, primarycontact, primaddrcode, "
LWyUEDH  
+"initialamt,createdate) values (0,?,?,?,?,TODAY)",conn);
.j6{4(Q.e  
insCmd.Parameters.Add("clientacctname", IfxType.Char,60,
}b w8WaZ  
"clientacctname");
-24It<X6  
insCmd.Parameters.Add("primarycontact", IfxType.Char, 30,
Lt1\K'qqa1  
"primarycontact");
)3=9\Px  
insCmd.Parameters.Add("primaddrcode", IfxType.Char, 10,
i8_2R-Y,  
"primaddrcode");
^rMBuNk  
insCmd.Parameters.Add("initialamt", IfxType.Decimal, 16,
l6Nt 4a?  
"initialamt");
;XSIB8\ h  
daclients.InsertCommand = insCmd;
3bJ,^7$X  
IfxDataAdapter 具有以下命令属性:SelectCommand、InsertCommand、DeleteCommand 和 UpdateCommand。当 IfxDataAdapter 执行 Fill() 方法时,它会使用 SelectCommand 命令来查询数据库。当调用 Update() 方法时,IfxDataAdapter 使用 Insert、Update 和 Delete 命令的组合来使数据库与表的内存版本相一致。IfxDataAdapter 决定哪些行和列需要更改;开发人员不必编写代码来追踪数据的更改。
q<-n5 6f%  
注意,可以将零插入序列值,这通常适用于 Informix 串行类型。但是,如何可以让数据库生成的值回到断开连接的 DataSet 中呢?您不必陷入 dsclientsIfxDataAdapter 的 RowUpdated 事件。在该事件的处理器中,代码看起来像是随意插入的。对于某个插入对象,执行 dbinfo 命令可以检索刚创建的序列值。处理器将该值放入 DataRow 的客户机代码列。以下是特定于此 Informix 技巧的事件处理器代码:
?)xJ o Y  
清单 11. 检索生成的序列值 fm:tp-n  
private void daclients_RowUpdated(object sender,
D<|(k NT  
                      IfxRowUpdatedEventArgs e) {
!=N"3 #]  
  //For INSERTs only, gets the serial id and
e LX#0 X"  
  //inserts into the clientcode
Si/1?8I4  
  if (e.StatementType == StatementType.Insert) {
aZ"~,#Fz2  
    IfxCommand getSerial = new IfxCommand(
E/}2@OY]  
      "select dbinfo(’sqlca.sqlerrd1′) from systables "
OS4FUV  
      +"where tabid = 1",
KF]++F^O  
      daclients.InsertCommand.Connection);
3"2a k]| ?  
    e.Row["clientcode"] = (int)getSerial.ExecuteScalar();
npWnF9′<  
  }
EJ]Y n wz  
}
>GE,Rr]`MK  
t<x]$ |HB  
fm*(RHf^5  
H’4P%|v%2  
回页首
s_ )’X
 
d\S\0 3  
结束语 4#k*gFUhH  
现在,您有一个完全封装的业务对象,它可以处理自己的数据库交互。那么现在您可以做些什么呢?因为 DataSet 衍生自 System.Component,所以您可以在 Visual Studio Toolbox 上添加强类型 DataSet 对象(例如,Client)。然后,您可以将它拖动到任何 WinForm 或 WebForm 设计视图上。请为 Connection 设置属性。在您的代码中执行对象的 Fill() 方法(可能在 FormLoad 事件中)。这会使用每个 DataTable 的所有数据填充该对象。在 Designer 视图中,还可以通过设置视觉控件或网格的 DataSource(以及 DataMember 属性)来实现数据绑定。
~KqK~Cp=  
示例解决方案中的 LibraryConsoleTest 程序演示了强类型 DataSet 的工作方式。您现在可以编写如下所示的代码:Console.WriteLine(client.clientcode + " " + client.clientacctname+" " + client.createdate);,而不是编写以下代码:Console.WriteLine(ds.Tables["clients"].Rows["clientcode"] + " " + ds.Tables["clients"].Rows["clientacctname"] + " " + ds.Tables["clients"].Rows ["createdate"] )。LibraryConsoleTest 添加了一台新客户机,并检索生成的序列号。在使用 Findbyclientcode() 方法选择正确的客户机之后,现在它要删除一台客户机。它还要更新特定行中的某一列。最后,它要循环遍历客户机,将数据输出到 Console。示例解决方案还包括一个快速 Windows Forms 应用程序 (WinFormsApp),它演示了如何使用 DataGrid 实现数据绑定。
{!/]<(%9TV  
对于大多数业务应用程序而言,数据访问是恒古不变的需要。然而,实现数据访问的模型和方法一直在变。本文中的例子将帮助您了解是否选择 ADO.NET 和 Informix Dynamic Server 作为您的工具。
}*O&z^w>  
9`r1X>%W  
L=jR?-q  
|&)Oy] Y2  
回页首
?Dk(%,d"q
 
y` k2-  
下载 PP9F5[Q8  
描述
DfJx<5>Y  
名字
d2w^6_k3  
大小
u l{K!  
下载方法
OND|/VS  
Sample Solution - all demo code
*/ kx*  
IfxAdo.zip
>H'y %DP9  
27 KB
|(zA0)t\  
f9,m0S&<  
CsT8  
FTP
Rva29?
 
IEdDfVg9~,  
,]0@!;}1  
d#f!OYE  
0mBE0IC"Z  
|
U{=2]L/|  
.exQFI(te  
tj,1L57<`  
HTTP
KMKT@Kr
 
09 #l?Q  
rK[ c:1D  
>D\>k=dX?  
e"sqrvXP%  
xi.M el3  
S7iEoE-  
#~1)!VPM\  
""kh{A  
关于下载方法的信息
8Q9[$  
vx:_<u  
D{=:s]TT  
1′_H>  
N.NL8  
}LPxl _q/  
)(GY[y5  
#i,mXoC  
5gr/3H]K(  
获取 Adobe® Reader®
VNhJ1uQ5[  

2006年04月26日

鉴于看到翠花每天孤单的小可怜样,于是我跟老婆给翠花找了个小老公,起名酸菜。酸菜先生现在已经满月了。

真是天妒红颜啊,美女翠花的老公酸菜先生的模样,唉,不说了。我都不好意思给他拍照片了。大家都说酸菜现在还没长开呢。那还是等过段时间酸菜同志长开以后再给他拍照片了。

最近几天一直在图书馆里面看《ADO.NET实用指南》,发现真是一本好书。读书自然就有心得,本人就根据书的线索,把自己的学习体会主要以代码的形式记录下来。(书上对应代码在http://www.adoguy.com/book里)

1、连接
ADO.NET最大的特色就在于支持在断开连接的情况下对数据库里的内容进行操作,这样可以大大的节约过多连接带来的消耗,前面的那一篇文章中已经给了一个具体的例子说明ADO.NET的这种特性。我们可以在从数据库里获得数据的时候打开连接,在得到数据之后就断开连接,对dataset里面的数据进行操作,然后在把dataset里的内容更新到数据库里面的时候再打开连接。对于dataReader则必须一直保持连接。
使用这种特性的时候有几点要注意一下:
(1)更改连接属性的时候必须断开连接
(2)切换数据库的时候选择conn.changeDatabase(dbName),减少断开连接与新建连接往返带来的消耗
ADO.NET同时支持数据库自带连接池。在一个连接关闭之后,连接会在池中保持一段时间,然后才实际的关闭,如果在超时之前,有人请求建立相同的连接,就将打开的连接分配给请求者,这对于经常进行打开和断开的连接可以减少很多的消耗。不过在SQL SERVER 2000中采用集成安全性的连接无法入池。
连接涉及到的事件有Dispose,InfoMessage,StateChange,在MSDN可以查到,不再赘述。
模板代码:
Dim conn As SqlConnection
conn=New SqlConnection("……") ”里面为连接字符串
conn.open()
”进行相应的操作
conn.close()

2、Command对象
ADO.NET允许以三种不同的方式获取数据库里面的数据Command,DataSet,DataReader,Command是最基本的方法,通过执行SQL命令的形式获得数据。

(1)创建 可以用两种方式创建
a、创建新的Command对象
Dim cmd As New SqlCommand
cmd.connection=conn
cmd.CommandText="SELECT * FROM Customer"
b、获得对conn中command对象的引用
Dim cmd As SqlCommand
cmd=conn.createCommand();
cmd.CommandText="SELECT * FROM Customer"
推荐第二种方法

(2)执行 四种执行方式
ExecuteNonQuery() 返回受命令影响的行数
ExecuteScalar() 返回第一行第一列(使用与集函数)
ExecuteReader() 返回一个DataReader对象
ExecuteXmlReader()返回一个XmlReader对象

(3)参数 主要是用在存储过程中,有复杂和精简两种形式
复杂方法:
Dim param As new SqlParameter("@Return",SqlDbType.Int)
param.Direction=ParameterDirection.ReturnValue
cmd.Parameters.Add(param)
精简方法
cmd.Parameters.Add("@Return_value",DbType.Int32).Direction=ParameterDirection.ReturnValue
建议:如果需要处理输出值时的时候使用参数,只处理输入值的时候就不用使用参数了。

(4)事务
用SQL语句:
Begin TRAN
SQL操作
If @@ERROR <>0
Begin
RollBack TRAN
Return @@ERROR
End
Commit TRAN
Return 0
在ADO.NET中编写事务
cmd.Transaction = conn.BeginTransaction()
try
{
cmd.CommandText="…"
cmd.ExecuteNonQuery()
cmd.Transaction.commit()
}
catch(Exception ex)
cmd.Transaction.Rollback()
End try
如果希望将数据库事务处理与一些外部系统结合起来(比如在数据库更新时同时进行了WEB更新,如果WEB更新失败希望回滚事务时),选择客户端编写事务处理(用ADO.NET编写)
仅仅做数据库事务处理的话就直接在服务端写事务语句(在SQL SERVER2000 中写事务)
在事务中可以创建SavePoint来实现部分事务回滚
cmd.Transaction.save("New Customer")
cmd.Transaction.Rollback("New Customer")

(5)批处理查询 如果有多条SQL语句并且可以一起执行的话,就可以进行批处理查询。在DataReader中支持批处理查询获得的数据集。
cmd.CommandText="SELECT * FROM Customer;SELECT * FROM Inovince;"
Dim rdr As SqlDataReader
rdr=cmd.ExecuteReader()
rdr中即包含两个SQL语句执行的结果

3、DataReader对象
DataReader对象只能对查询获得的数据集进行自上而下的访问,但效率很高。如果仅仅是访问数据的话,可以使用DataReader。但DataReader要求一直连接,所以将结果的一小部分先放在内存中,读完后再从数据库中读取一部分,相当于一个缓存机制。这对于查询结果百万级的情况来说,带来的好处是显而易见的。
模板代码:
Do While rdr.Read()
Console.WriteLine(rdr(0)) ”也可输出rdr("CustomerID")
Loop
如果要进行类型限制的话可以输出(String)rdr(0)或rdr.GetString(0)

从DataReader里读出数据的时候要注意属性是否为空,如果属性可以为空,则读出数据时应进行判断
If Not rdr.IsDBNull(0)
Console.writeLine(…)

用DataReader读取记录时,数据库默认上锁,可以通过更改DataReader的默认属性改变。

如果DataReader里面的数据是批处理语句执行得到的话,可以通过NextResult访问
模板代码:
Do
Do While rdr.Read()
Console.WriteLine(rdr(0))
Loop
Loop While rdr.NextResult()

处理元数据(显示每个属性的情况)
Dim schema As DataTable
schema=rdr.GetSchemaTable() ”得到元数据表,表里的每列对应每个属性的特性集合
对于每一列属性对应的row,通过row("DataType")获得这列属性的数据类型。

4、DataSet
在ADO.NET中DataSet的作用是为数据源提供一个断开式的存储,而不必关心数据源,操作只用在DataSet中进行就行了。
有三种方法可以创建DataSet:1、通过DataAdapter 2、通过XML 文件 3、用人工方法确定架构,然后逐行输入数据。
主要介绍第一种方法。
DataAdapter用于将DataSet连接到基本数据存储,本质上是一种元Command对象。
它包括SelectCommand对象,InsertCommand对象,UpdateCommand对象,DeleteCommand对象。
模板代码:
Dim dataAdpater As New SqlDataAdapter("Select * From Student",conn)
Dim dataSet As New DataSet()
dataAdapter.Fill(dataSet)
这时dataSet的表名默认为Table
如果使用批处理查询并将得到的结果填入dataSet中则表名默认为Table,Table1,Table2……

TableMappings:
表名映射:
生成dataAdapter之后再进行表名映射
dataAdapter.TableMappings.Add("Table","Customer")
dataAdapter.Fill(dataSet)
这时Table的别名就变为Customer(对dataSet用Table或Customer操作都可以),dataSet.Tables("Customer")就可以引用到这个表

dataAdapter.TableMappings.Add("ADONET","Customer")
dataAdapter.Fill(dataSet,"ADONET")
列名映射:
dataAdapter.TableMappings.Add("Table","Customer")
dataAdapter.TableMappings("Customer").ColumnMappings.Add("CustomerID","ID)
dataAdapter.FIll(dataSet,"Customer")

架构(Schema),通过FillSchema添加架构
1、添加主键
customerTable.PrimaryKey=New DataColumn[]{CustomerTable.Columns("CustomerID")} (通过数组的形式添加主键)

2、添加关系
dataSet.Relations.Add("Customers_Invoices",dataSet.Tables("Customers").Columns("CustomerID"),dataSet.Tables ("Invoinces").Columns("CustomerID"),true)

3、添加约束
有两种主要约束:唯一约束、外码约束(UniqueConstraint,ForeignKeyConstraint)
其中唯一约束又分为DeleteRule(级联删除约束)、UpdateRule(级联更新约束)、AcceptRejectRule(调用AcceptChanges或 RejectChanges时的约束)

4、添加触发器
可以对6中DataSet事件添加触发器
RowChanging,RowChanged,ColumnChanging,ColumnChanged,RowDeleting,RowDeleted

5、列架构
比如添加列的属性:customerTable.Columns("CustomerID").ReadOnly=true
或添加AutoIncrement列:
customerTable.Columns("CustomerID").AutoIncrement=true
customerTable.Columns("CustomerID").AutoIncrementSeed=1 (列起始位置)
customerTable.Columns("CustomerID").AutoIncrementStep=1 (列递增步长)
可能有人会说这一切在DBMS里面做不就行了吗,干吗那么大费周折的在ADO.NET对Dataset再写一遍呢?
这主要是出于对效率方面的考虑,如果客户端的错误输入能在客户端就被发现出来,而不用传到服务端进行验证的话就可以减少不必要的传输了。

表达式列:
Dim exColumn As New DataColumn("LineTotal")
exColumn.DataType=typeof(float)
exColumn.Expression="((price-(price*Discount))*Quantity)"
dataSet.Tables("items").Columns.Add(excolumn)

(由于书上的代码全是用C#写的,转换为VB.NET实在麻烦,以后就不转换了,见谅)
五、操纵dataset
在DataSet中DataRow是其所有数据的基本存放位置,它主要是由一个值数组组成,代表DataTable单独一行。
DataRow中主要包括一下几种信息:1、行中每一列的当前值,2、行中每一列的原始值,3、行状态,4、父行与子行间的链接

初始化一个DataRow:
DataTable dataTable=dataSet.Tables[0];
DataRow newRow=dataTable.NewRow(); //用dataTable生成DataRow可以利用dataTable里面的模式
dataTable.Rows.Add(newRow);

删除行:
DataTable.Rows.Remove(行实例);
DataTable.Rows.RemoveAt(行号);
DataRow.Delete(); //行自身移除

读写DataRow的值:
row["列名"],row[列号]均可引用其中的一个属性
DataColumn a=dataTable.Columns("列名"); //可以获得一个列

对行进行批处理更改:
BeginEdit()开始更改,EndEdit()结束更改,同时将更改结果写入DataSet,CancelEdit(),取消更改
例如:
row.BeginEdit();
对row进行更改
row.EndEdit();

将数据批量加载到DataTable
dataTable.BeginLoadData();
dataTable.LoadDataRow(row1,false); //第二个参数为true时,调用dataTable.AcceptChanges()时接受更改,为false直接添加
……
dataTable.EndLoadData();
使用这种数据加载方式可以在数据加载期间屏蔽所有的数据约束,索引也不会予以维护,极大的加快了数据加载速度

行的版本:
current:当前值
default:根据操作的不同决定行的default值
original:最后一次调用AcceptChanges()之后的值
proposed:调用AcceptChanges()之前被更改的值
例如要获得行的original值:
String oldString=row("FirstName",DataRowVersion.original);

行的状态:
row.RowState获得行的状态,例如删除后变成Deleted,数据存储更新后变为unchanged

六、DataSet导航
在ADO.NET中每个表都保持其相对独立性,允许在行级上导航不同表之间的相关行(向下导航到子行,向上导航的父行)
如DataRow[] invoiceRows=custRow.GetChildRows("Customer_invoice"); //通过关系导航到子行

七、DataView
DataView就时数据视图,为数据库结构提供了外模式的实现。
同时DataView也可以为窗体控件和Web控件提供数据绑定功能,在每一个DataTable中内建了一个DataView为:DataTable.DefaultView();

创建DataView
DataView sortedView=new DataView(dataTable);

对DataView进行排序
dataTable.DefaultView.sort="lastName";
dataTable.DefaultView.sort="lastName,FirstName DESC";

对DataView进行筛选:
1、通过对其中的RowFilter属性设置可以实现筛选
dataTable.DefaultView.RowFilter="Vendor=”Rawlings”";
不过筛选表达式只能设置成比较简单的表达式,功能有限,不过可以满足基本的要求。
同样在DataTable里面也可以进行简单的搜索,返回一个DataRow数组,例:
DataRow[] compoundRows=dataTable.select("Vendor=”wilson” AND price>20.00)
2、通过RowState来筛选
dataTable.DefaultView.RowStateFilter="DataViewRowState.originalRows"可以筛选出符合要求状态的row

对DataView进行搜索:
相对于DataView使用RowFilter进行筛选得到一个矩形数据集,使用Find、FindRows可以更准确的查找到与特定键相匹配的行
搜索的时候必须首先设置DataView的sort属性:
int found=dataTable.DefaultView.Find("wilson"); //获得行的位置
DataRowView[] rows=dataTable.DefaultView.FindRows("Rawlings") //过得一个row数组

八、更新DB
在DataSet中,每一个DataTable对应着一个DataAdapter,DataAdapter.Update()时,DataTable自动更新。
更新的时候可以使用CommandBuilder自动根据DataSet的变化生成更新的SQL命令
SqlCommandBuilder bldr=new SqlCommandBuilder(dataAdapter);
dataAdapter.Update(custTable);
不过Update接受DataSet参数并不更新DataSet而是更新DataSet中的一个叫"Table"的表
使用CommandBuilder进行更新的时候要注意一下几点:
1、SelectCommand必须有效
2、必须有主码
3、若SelectCommand填充DataTable后架构发生改变,应该在Update()之前调用CommandBuilder.RefreshSchema();
4、更新DB时不受关系、约束或者DataSet中其他表的影响
虽然使用CommandBuilder比较方便,不用自己写更新命令,但自动生成的命令性能不高,这时可以考虑自己编写存储过程或直接使用带参数的sql语句,例如:
String insQry="Insert into Customer(CustomerID) Values (@Customer)";
SqlCommand insCmd=conn.CreateCommand();
insCmd.CommandText=insQry;
SqlParameterCollection insParams=insCmd.Parameters;
insParams.Add("@CustomerID",SqlDbType.UniqueIdentifier,0,"CustomerID");
dataAdapter.InsertCommand=insCmd;
dataAdapter.Update();
在dataAdapter.Update()更新时还可以控制更新的范围:
dataAdapter.Update(invTable.GetChanges(DataRowState.Deleted); //只更新被删除的部分

九、事务
tx=conn.BeginTransaction(IsolationLevel.Serializable);
invDA.SelectCommand.Transaction=tx;
事务操作
tx.Commit();提交 //tx.Rollback();事务回滚

十、数据绑定
简单版本:(对文本框、标签等)
{Controls}.DataBindings.Add("{Property}",{dataSource},"{dataMember}");
其中Property为待绑定的属性,dataSource为DataView或DataTable,dataMember为dataSource其中的某个属性

复杂版本:(对ListBox、ComboBox等)
要分别设置各个属性实现绑定:
DataSource=支持IList的一个对象(DataTable或DataView)
DisplayMember:待显示的DataSource中的一个属性
ValueMember:确定在DataSource中引用哪一个数据行,即实现与DisplayMember的名值对应

DataGrid绑定:
1、可以设置DataSource属性实现静态绑定
2、可以使用SetDataBinding函数实现动态绑定

同时DataGrid支持主控/详细表显示
masterGrid.setDataBinding(customerTable,"");
detailGrid.setDataBinding(customerTable,"Customer_Invoices") //第二个属性要设置成关系约束
这样在主表中选择一行,在子表中就根据主表行中外码在子表中找到相应行

绑定之后,绑定项中就有一个CurrencyManager属性实现游标功能
BindingContext[CustomerTable]返回一个CurrencyManager对象,其中的Position属性可以更改,实现游标的移动。