posts - 49,comments - 87,trackbacks - 13

摘要: 编程过程中,常常会听说什么变量时在栈上,什么变量是在堆上,那么究竟什么是栈,什么是堆呢?我发现了一篇文章讲的比较好,收藏一下(http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx),具体类容转载如下:Part1Eventhough with the .NE...阅读全文
posted @ 2009-08-29 10:06 Liu Jian 阅读(307) 评论(0) 编辑
摘要: A low-level Look at the ASP.NET ArchitectureGetting Low LevelThis article looks at how Web requests flow through the ASP.NET framework from a very low level perspective, from Web Server, through ISAPI...阅读全文
posted @ 2009-08-16 22:47 Liu Jian 阅读(225) 评论(0) 编辑

加载.NET-(稍微有点神秘)

    让我们回到之前略过的一个话题:当请求到达时,.NET运行时是如何被加载的。具体在哪里加载的,这是比较模糊的。关于这个处理过程,我没有找到相关的文档,由于我们现在讨论的是本地代码,所以通过反编译ISAPI DLL文件并把它描述出来显得不太容易。

    最佳猜测是,在ISAPI扩展里,当第一个请求命中一个ASP.NET的映射扩展时,工作线程就会引导.NET运行时启动。一旦运行时存在了,非托管代码 就可以为指定的虚拟目录请求一个ISAPIRuntime对象的实例,当然前提条件是,这个实例还不存在。每一个虚拟目录都会拥有一个 AppDomain,在ISAPIRuntime存在的AppDomain里,它将引导一个单独的程序启动。由于接口被作为COM可调用的方法暴露,所以 实例化操作将发生在COM之上。

    为了创建ISAPIRuntime的实例,当指定虚拟目录的第一个请求到达 时,System.Web.Hosting.AppDomainFactory.Create()方法将被调用。这将会启动程序的引导过程。这个方法接收 的参数为:类型,模块名以及应用程序的虚拟路径,这些都将被ASP.NET用于创建AppDomain,接着会启动指定虚拟目录的ASP.NET程序。 HttpRuntime的根对象将会在一个新的AppDomain里创建。每一个虚拟目录或者ASP.NET程序将寄宿在属于自己的AppDomain 里。它们仅仅在有请求到达时启动。ISAPI扩展管理这些HttpRuntime对象的实例,然后基于请求的虚拟路径,把请求路由到正确的应用程序里。

回到运行时

    这个时候已经拥有了一个ISAPIRuntime的活动实例,并且可以在ISAPI扩展里调用。一旦运行时启动并运行起来,ISAPI扩展就可以调用 ISAPIRuntime.ProcessRequest()方法了,而这个方法就是进入ASP.NET通道真正的登录点。图1展示了这里的流程。



    图1把ISAPI的请求转到ASP.NET通道需要调用很多没有正式文档的类和接口,以及几个工厂方法。每一个Web程序/虚拟目录都运行在属于自己的 AppDomain里。调用者将维护一个IISAPIRuntime接口的代理引用,负责触发ASP.NET的请求处理。记住:ISAPI是多线程的,因 此请求可以以多线程的方式穿过AppDomainFactory.Create()返回的对象引用。列表1展现了从 IsapiRuntime.ProcessRequest方法反编译得到的代码。这个方法接收一个ISAPI ecb对象和一个服务器类型参数(这个参数用于指定创建何种版本的ISAPIWorkerRequest),这个方法是线程安全的,因此多个ISAPI线 程可以同时安全的调用单个返回对象的实例。

    列表 1: ProcessRequest请求进入 .NET的登录点


public int ProcessRequest(IntPtr ecb, int iWRType)
{
// ISAPIWorkerRequest从HttpWorkerRequest 继承,这里创建的是
// ISAPIWorkerRequest派生类的一个实例
HttpWorkerRequest request1 =
ISAPIWorkerRequest.CreateWorkerRequest(ecb,iWRType);
//得到请求的物理路径
string text1 = request1.GetAppPathTranslated();
//得到AppDomain的物理路径
string text2 = HttpRuntime.AppDomainAppPathInternal;
if (((text2 == null) || text1.Equals(".")) ||
(string.Compare(text1, text2, true,
CultureInfo.InvariantCulture) == 0))
{
HttpRuntime.ProcessRequest(request1);
return 0;
}
//如果外部请求的AppDomain物理路径和原来AppDomain的路径不同,说明ISAPI维持
//的AppDomain的引用已经失效了,所以,需要把原来的程序关闭,当有新的请求时,会
//再次启动程序。
HttpRuntime.ShutdownAppDomain("Physical path changed from " +
text2 + " to " + text1);
return 1;
}

需要提醒的是,这里的代码是通过反编译.NET框架内的代码得到的,我们永远也不会和这些代码打交道,而且这些代码以后可能会有所变动。这里的用意是揭示 ASP.NET在底层发生了什么。ProcessRequest接收了非托管参数ecb的引用,然后把它传给了ISAPIWorkerRequest对 象,这个对象负责创建当前请求的内容。如列表2所示。

    列表2: 一个ISAPIWorkerRequest 的方法

// *** ISAPIWorkerRequest里的实现代码 
public override byte[] GetQueryStringRawBytes()
{
byte[] buffer1 = new byte[this._queryStringLength];
if (this._queryStringLength > 0)
{
int num1 = this.GetQueryStringRawBytesCore(buffer1,
this._queryStringLength);
if (num1 != 1)
{
throw new HttpException( "Cannot_get_query_string_bytes");
}
}
return buffer1;
}

// *** 再派生于ISAPIWorkerRequest的类ISAPIWorkerRequestInProcIIS6的实现// *** 代码
// *** ISAPIWorkerRequestInProcIIS6
internal override int GetQueryStringCore(int encode, StringBuilder
buffer, int size)
{
if (this._ecb == IntPtr.Zero)
{
return 0;
}
return UnsafeNativeMethods.EcbGetQueryString(this._ecb, encode,
buffer, size);
}


    System.Web.Hosting.ISAPIWorkerRequest继承于抽象类HttpWorkerRequest,它的职责是创建一个抽象 的输入和输出视图,为Web程序的输入提供服务。注意这里的另外一个工厂方法CreateWorkerRequest,它的第二个参数用于指定创建什么样 的工作请求对象(即ISAPIWorkerRequest的派生类)。这里有3个不同的版 本:ISAPIWorkerRequestInProc,ISAPIWorkerRequestInProcForIIS6,ISAPIWorkerRequestOutOfProc。 当请求到来时,这个对象(指ISAPIWorkerRequest对象)将被创建,用于给Request和Response对象提供基础服务,而这两个对 象将从数据的提供者WorkerRequest接收数据流。

    抽象类HttpWorkerRequest围绕着底层的接口提供了高层的抽象(译注:抽象的目的是要把数据的处理与数据的来源解藕)。这样,就不用考虑数 据的来源,无论它是一个CGI Web Server,Web浏览器控件还是你自定义的机制(用于把数据流入HTTP运行时),ASP.NET都可以以同样的方式从中获取数据。

   有关IIS的抽象主要集中在ISAPI ECB块。在我们的请求处理当中,ISAPIWorkerRequest依赖于ISAPI ECB,当有需要的时候,会从中读取数据。列表2展示了如何从ECB里获取查询字符串的值的例子。

   ISAPIWorkerRequest实现了一个高层次包装器方法(wrapper method),它调用了低层次的核心方法,而这些方法负责实际调用非托管API或者说是“服务层的实现”。核心的方法在 ISAPIWorkerRequest的派生类里得以实现。这样可以针对它宿主的环境提供特定的实现。为以后增加一个额外环境的实现类作为新的Web Server接口提供了便利。同样使ASP.NET运行在其它平台上成为可能。另外这里还有一个帮助 类:System.Web.UnsafeNativeMethods。它的许多方法是对ISAPI ECB进行操作,用于执行关于ISAPI扩展的非托管操作。

HttpRuntime,HttpContext以及HttpApplication

   当一个请求到来时,它将被路由到ISAPIRuntime.ProcessRequest()方法里。这个方法会接着调用 HttpRuntime.ProcessRequest,在这个方法里,做了几件重要的事情(使用Refector反编译 System.Web.HttpRuntime.ProcessRequestInternal可以看到)。

   为请求创建了一个新的HttpContext实例 

   获取一个HttpApplication实例 
   调用HttpApplication.Init()初始化管道事件
   nit()触发HttpApplication.ResumeProcessing(),启动ASP.NET管道处理
   首先,一个新的HttpContext对象被创建,并且给它传递一个封装了ISAPI ECB 的ISAPIWorkerRequest。在请求的生命周期里,这个上下文(context)一直是有效的。并且可以通过静态的 HttpContext.Current属性访问。正如它的名字暗示的那样,HttpContext对象表示当前活动请求的上下文,因为它包含了在请求生 命周期里你会用到的所有必需对象的引用,如:Request,Response,Application,Server,Cache。在请求处理过程的任 何时候,你都可以使用HttpContext.Current访问这些对象。

    HttpContext对象还包含了一个非常有用的列表集合,你可以使用它存储有关特定的请求需要的数据。上下文(context)对象创建于一个请求生 命周期的开始,在请求结束时被释放。因此,保存在列表集合里的数据仅仅对当前的请求有效。一个很好的例子,就是记录请求的日志机制,在这里,通过使用 Global.asax里的Application_BeginRequest和Application_EndRequest方法,你可以从请求的开始 时间至结束时间段内,对请求进行跟踪。如列表3所示。记住HttpContext在请求或者页面处理的不同阶段,如果需要相关数据都可以使用它获取。

    列表 3: 通过在通道事件里使用HttpContext.Items 集合保存数据

protected void Application_BeginRequest(Object sender, EventArgs e) 
{
//*** Request Logging
if (App.Configuration.LogWebRequests)
Context.Items.Add("WebLog_StartTime",
DateTime.Now);
}

protected void Application_EndRequest(Object sender, EventArgs e)
{
// *** Request Logging
if (App.Configuration.LogWebRequests)
{
try
{
TimeSpan Span = DateTime.Now.Subtract(
(DateTime)Context.Items["WebLog_StartTime"]);
int MiliSecs = Span.TotalMilliseconds;

// do your logging
WebRequestLog.Log(
App.Configuration.ConnectionString,
true,MilliSecs);
}
}


    一旦请求的上下文对象被搭建起来,ASP.NET就需要通过一个HttpApplication对象,把你的请求路由到合适的程序/虚拟目录里。每一个ASP.NET程序都拥有各自的虚拟目录(Web根目录),并且它们都是独立处理请求的。

Web程序的主要部分:HttpApplication

   每一个请求都将被路由到一个HttpApplication对象。HttpApplicationFactory类会为你的ASP.NET程序创建一个 HttpApplication对象池,它负责加载程序和给每一个到来的请求分发HttpApplication的引用。这个 HttpApplication对象池的大小可以通过machine.config里的ProcessModel节点中的 MaxWorkerThreads选项配置,默认值是20。

    HttpApplication对象池尽管以比较少的数目开始启动,通常是一个。但是当同时有多个请求需要处理时,池中的对象将会随之增加。而 HttpApplication对象池,也将会被监控,目的是保持池中对象的数目不超过设置的最大值。当请求的数量减小时,池中的数目就会跌回一个较小的 值。

    对于Web程序而言,HttpApplication是一个外部容器,它对应到Global.asax文件里定义的类。基于标准的Web程序,它是实际可 以看到的进入HTTP运行时的第一个登录点。如果你查看Global.asax(后台代码),你就会看到这个类直接派生于 HttpApplication。

public class Global : System.Web.HttpApplication

    HttpApplication主要用作HTTP管道的事件控制器,因此,它的接口主要有事件组成,这些事件包括:
BeginRequest
AuthenticateRequest
AuthorizeRequest
ResolveRequestCache
   [此处创建处理程序(即与请求 URL 对应的页)。]
AcquireRequestState
PreRequestHandlerExecute
   [执行处理程序。]
PostRequestHandlerExecute
ReleaseRequestState
   [响应筛选器(如果有的话),筛选输出。]
UpdateRequestCache
EndRequest
    这里的每一个事件都在Global.asax文件中以Application_为前缀,无实现代码的方法出现。举个例子,如 Application_BeginRequest()和Application_AuthorizeRequest()。由于它们在程序中会经常用到, 所以出于方便的考虑,这些事件的处理器都已经被提供了,这样你就不必再显式的创建这些事件处理器的委托了。

    每一个ASP.NET Web程序运行在各自的AppDomain里,在AppDomain里同时运行着多个HttpApplication的实例,这些实例存放在 ASP.NET管理的一个HttpApplication对象池里,认识到这一点,是非常重要的。这就是为什么可以同时处理多个请求,而这些请求不会互相 干扰的原因。

使用列表4的代码,可以进一步了解AppDomain,线程,HttpApplication之间的关系。

   列表 4: AppDomain, Threads and HttpApplication instances之间的关系

private void Page_Load(object sender, 
System.EventArgs e)
{
// Put user code to initialize the page here
this.ApplicationId = ((HowAspNetWorks.Global)
HttpContext.Current.ApplicationInstance).ApplicationId;

this.ThreadId = AppDomain.GetCurrentThreadId();

this.DomainId =
AppDomain.CurrentDomain.FriendlyName;

this.ThreadInfo = "ThreadPool Thread: " +
Thread.CurrentThread.IsThreadPoolThread.ToString() +
"<br>Thread Apartment: " +
Thread.CurrentThread.ApartmentState.ToString();

// *** 为了可以同时看到多个请求一起到达,故意放慢速度
Thread.Sleep(3000);
}


   这是样例程序的一部分,运行的结果如图5所示。为了检验结果,你应该打开两个浏览器,输入相同的地址,观察那些不同的ID的值。



    图2同时运行几个浏览器,你会很容易的看到AppDomains,application对象以及处理请求的线程之间内在的关系。当多个请求触发时会看到线程和application的ID在改变,而AppDomain的ID却没有发生变化。

    观察到AppDomain ID一直保持不变,而线程和HttpApplication的ID在请求多的时候会发生改变,尽管它们会出现重复。这是因为 HttpApplications是在一个集合里面运行,下一个请求可能会再次使用同一个HttpApplication实例,所以有时候 HttpApplication的ID会重复。

    注意:一个HttpApplication实例对象并不依赖于一个特定的线程,它们仅仅是被分配给处理当前请求的线程而已。

    线程由.NET的ThreadPool提供服务,默认情况下,线程模型为多线程单元(MTA)。你可以通过在ASP.NET的页面的@Page指令里设置 属性ASPCOMPAT="true"覆盖线程单元的状态。ASPCOMPAT意味着COM组件将在一个安全的环境下运行。ASPCOMPAT使用了单线 程单元(STA)的线程为请求提供服务。STA线程在线程池里是单独设置的,这是因为它们需要特殊的处理方式。

    实际上,这些HttpApplication对象运行在同一个AppDomain里是很重要的。这就是ASP.NET如何保证web.config的改变 或者单独的ASP.NET页面得到验证可以贯穿整个AppDomain。改变web.config里的一个值,将导致AppDomain关闭并重新启动。 这确保了所有的HttpApplication实例可以看到这些改变,这是因为当AppDomain重新加载的时候,来自ASP.NET的那些改变将会在 AppDomain启动的时候重新读取。当AppDomain重新启动的时候任何静态的引用都将重新加载。这样,如果程序是从程序的配置文件读取的值,这 些值将会被刷新。

   在示例程序里可以看到这些,打开一个ApplicationPoolsAndThreads.aspx页面,注意观察AppDomain的ID。然后在 web.config里做一些改动(增加一个空格,然后保存),重新加载这个页面(译注:由于缓存的影响可能会在原来的页面上刷新无效,需要先删除缓存再 刷新即可),你就会看到一个新的AppDomain被创建了。

    本质上,这些改变将引起Web程序重新启动。对于已经存在于处理管道的请求,将继续通过原来的管道处理。而对于那些新的请求,将被路由到新的 AppDomain里。为了处理这些“挂起的请求”,在这些请求超时结束之后,ASP.NET将强制关闭AppDomain甚至某些请求还没有被处理。因 此,在一个特定的时间点上,同一个HttpApplication实例在两个AppDomain里存在是有可能的,这个时间点就是旧的AppDomain 正在关闭,而新的AppDomain正在启动。这两个AppDomain将继续为客户端请求提供服务,直到旧的AppDomain处理完所有的未处理的请 求,然后关闭,这时候才会仅剩下新的AppDomain在运行。

 

 

 

posted @ 2009-08-16 22:26 Liu Jian 阅读(159) 评论(2) 编辑
摘要: 前言:前一段时间写Web Control开发系列的文章,后来由于工作实在忙,就没有继续写了,如今我要继续写下去,研究了微软的Web Control体系结构这么久,我有一个总体的感觉,就是微软把所有自己认为有用的东西,无论大小,都设计了,都实现了,以至于我们能发挥的空间很有限了,一旦我们设计一个自认为更好的结构,虽然确实很好,但是因为和微软的结构不一致,也会很难和微软的其它Control协同工作,所...阅读全文
posted @ 2008-11-13 14:45 Liu Jian 阅读(1838) 评论(5) 编辑
这篇文章起源于在公司写的一个PPT,但是由于PPT本身的限制很多内容无法表达或是详细的解释,于是变下定了决心。写篇文档!

  在这篇文章里我将尽量简单的描述下ADO.NET 2.0的新特性,尤其是配合SQL Server 2005所展现出来的强大实力。如果想进一步了解ADO.NET 2.0编程方面的话,可以去阅读Glenn Johnson的--"ADO.NET 2.0高级编程[微软推荐丛书] ".定价:46元,网络购书的话打了折只要30块就可以了。


  一:功能强大的ADO2.0

  2005年底(2005年10月)与 SQL Server 2005一起出现的是 .NET Framework 2.0 版本,其中用来访问数据库的 ADO.NET类也升级到 ADO.NET 2.0 版。


  ADO.NET 2.0 除了增强旧功能外,也提供了相当多的新功能,包含了以基础类为本(base-class-based)的数据源提供程序(provider)模型、异步访问架构、批处理更新与大量数据复制(bulk copy)、SQL Server 2005 的回调通知、单一连接同时多执行结果集(MARS)、执行统计、强化的 DataSet 类等等。换句话说,若要有效发挥 SQL Server 2005 的功能,前端应用程序最好用 ADO.NET 2.0 来开发。


  ADO.NET 2.0 提供了相当多的新增功能,一些与数据源提供程序无关,也就是访问各种数据库都可以用到的功能,但有很大的一部分是专属于 SQL Server 2005,针对 SQL Server 2005 的新功能提供给前端应用程序开发使用。


  二: 使用多数据结果集(仅限2005)

  在之前版本的 SQL Server 同一时间一条连接只能传递一个 SELECT 语法执行后返回的结果集。如果想在一次连接后返回多个查询内容只能使用类似如下的方法来实现:
SqlDataAdapter myDataAdapter = new SqlDataAdapter("StoredProcedureName",myConnection); myDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure; myDataAdapter.SelectCommand.Parameters.Add("@sqlstr",sqlstr); DataSet ds = new DataSet(); myDataAdapter.Fill(ds); return ds; ds.Tables[0],ds.Tables[1],ds.Tables[2],分别对应三个结果集
  SQL Server 2005提供了在同一条连接上可以同时传递多个没有游标结构(cursorless)的结果集(也称为默认结果集),此功能称为 Multiple Active Resultsets(MARS)。如此可以节省需要同时打开的连接数,但要注意的是连接字符串设置要加上 MultipleAct-iveResultSets=true 属性,否则默认不启动多数据结果集的功能。
string connstr = "server=(local);database=northwind;integrated security=true; "; SqlConnection conn = new SqlConnection(connstr); conn.Open(); SqlCommand cmd1 = new SqlCommand("select * from customers", conn); SqlCommand cmd2 = new SqlCommand("select * from orders", conn); SqlDataReader rdr1 = cmd1.ExecuteReader(); // next statement causes an error prior to SQL Server 2005 SqlDataReader rdr2 = cmd2.ExecuteReader(); // now you can reader from rdr1 and rdr2 at the same time.

 

三:异步执行Command命令

  在 ADO.NET 2.0 以前,通过 Command 类(如 SqlCommand、OleDbCommand等)执行 SQL

  命令的线程一定要停下来等待执行结果。ADO.NET 2.0 新增了异步程序访问接口(asynchronous API),让线程发出命令后可以继续执行接下去的程序代码。

  而在 ADO.NET 2.0 当前的版本只有 SqlClient 支持异步程序访问接口。

  以往编写程序时,我们可以直接通过.NET Framework 所提供的多线程机制,或是以 Delegate 类包装多线程的方式,在 .NET Framework 所提供的异步架构下,设计调用执行 Command 对象实例。这些方法都是让一条工作线程(Worker Thread)停止在后台中等待执行结果,一旦有结果后,工作线程再通过标准的机制告知结果。


  原本 ADO.NET 的 Command 对象执行 SQL 语法的方法有

  ExecuteReader、ExecuteNonQuery、ExecuteXmlReader 以及 ExecuteScalar 等,搭配 .NET

  Framework 原来就提供的异步模型惯例,除了 ExecuteScalar 方法外,其余的方法都新增了以 Begin 和 End 关键字开始的一对方法。也就是说 ExecuteReader 方法是同步执行,若要以异步的方式执行相同的功能,则调用 BeginExecuteReader 和 EndExecuteReader 这一组方法。在 .NET Framework 中,以 Begin 为字首的方法负责传入同名方法所需的参数,而以 End

  为字首的方法用来取回执行结果,

  例如某个方法的定义如下:

  public override int ExecuteNonQuery()

  则以异步调用的起始方法定义如下:

  public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObject)

  Begin~ 系列的方法会多加存放回调方法(Delegation)的指针参数,也就是上述语法中的 callback 参数。并提供语法中的 stateObject参数,让你设置想要带到 End~ 对应方法的信息。而 Begin~ 系列方法最后返回的是代表异步执行状态的 IAsyncResult 对象实例,而不是原本同步执行方法的返回结果,你可以藉此查询异步执行的状况。


  而获得执行结果的方法定义如下:

  public int EndExecuteNonQuery(IAsyncResult asyncResult)

  在调用与 Begin~ 对应的 End~ 方法时,需要带入 Begin~ 方法所返回的 IAsyncResult

  对象实例。异步执行完毕后,取回与原先同步执行方法相同的执行结果。


  由于我们在执行完 Command 对象访问数据库的方法后,都会返回对象,如 ExecuteReader 取回 DataReader实例;ExecuteNonQuery 取回受影响的记录条数;ExecuteXmlReader 取回 XmlReader 实例。因此大概都需要通过End系列方法来获得执行结果,否则这些结果就遗失在系统中。

  若要异步执行 Command 命令,另一个必需设置的是:数据库连接字符串内要加上 async=true 属性。若连接字符串没有加上该属性,而通过 Command对象实例调用异步执行的方法,则会产生异常(exception)。若 Command 通过连接执行时,重头到尾都是以同步的方式执行,则依照默认 async=false 的方式设置比较节省资源。若某些命令需要同步执行,另一些需要异步执行,则可以考虑使用不同的连接。

  在介绍范例应用程序前,我们先稍微谈一下 .NET Framework 所提供的公共的异步运行应用程序设计模式,不只是 ADO.NET2.0,在其他访问耗时的程序编写上,也都可以套用这个模式。

  .NET Framework内置了让应用程序异步运行的功能,让你在编写应用程序时,不会因为某些耗时等待的操作让程序停止响应,操作界面停滞让用户感觉起来好像死机一样。一般会以多线程的方式处理这种需求,但若你不熟悉线程的运行,或是想利用线程池(Thread Pool)的好处,都可以在较为耗时的操作上,采用 .NET Framework 所提供的异步功能。

  一般来说文件 I/O、网络访问乃至于 Web Services 访问,以及本节所讨论的 DB 访问等都较为耗时,.NET Framework为这一类的类都提供了上述以 Begin~/End~开头的非同步执行方法,而这些方法皆成对出现。当然,也有可能是自己编写的方法其商业逻辑非常复杂,导致调用该方法后,需要等待一段时间来完成,这时还可以通过 .NETFramework 所提供的委托(Delegate)类来创建异步运行。


  但是实际在我们的应用中,
 
  但我们不需要获知DB服务器的返回信息时,我们推荐使用委托,尤其是在Web开发中。

  因为在页面线程启动异步数据库访问时,当页面业务执行完毕后仍然无法放开访问数据库的异步线程。这是我们不希望看到的,但是使用委托却可以避免这个麻烦(webservice异步应用中一样如此)。

四:使用SqlBulkCopy批量装载数据(仅限SqlClient)

  以往访问 SQL Server 2000 时,若有大量的数据记录需要添加到数据库内,例如从主机系统或是 NCR Teradata、Oracle等数据库系统下载大量数据记录,我们想要将它们快速添加到 SQL Server 2000中,可以有的选择是调用 T-SQL 的 Bulk Insert 语法、通过Linked Server 执行 SELECT INTO 语法或是执行 bcp.exe 工具程序,以及通过 DTS 的 Bulk Insert Task 或启动Transform Data Task 的快速装载(Use Fast Load)设置。


  但若要通过自行编写的程序完成批次装载,只能以 C/C++ 调用 OLEDB 或 ODBC 的 Bulk API,无法通过 ADO.NET 或 ADO 等对象来执行。

  ADO.NET 2.0 的 SqlClient 提供了一个新的类称为 SqlBulkCopy,它让 DataSet 内大量的数据或是 DataReader通过数据流(Stream)直接读取大量的记录,可以快速将这些记录添加到目的数据库的数据表中。但要注意的是它并非如我们一般用的 bcp.exe工具程序,可以从某个符号分隔文件读取大量数据,选择性地搭配格式文件(Format File)将记录装载到数据库中,或是将数据库内的数据导出成为一个文件。但由于DataSet 能集成 XML 数据,因此依然可以采用 SqlBulkCopy 类型,轻松地通过 DataSet 将 XML 文件数据大量转入到数据库。



  可以利用SqlBulkCopy类快速写入大批量数据,针对SQL Server的优化,可以写入DataRow数据,DataTable,DataReader,并且可以映射不同的数据列名

  WriteToServer(DataTable)写入数据表

  WriteToServer(DataRow[])批次写入数据行

  WriteToServer(DataTable ,DataRowState)按行状态写入数据库表

  WriteToServer(IDataReader)写入DataReader对象


  下面是个示例:

using (SqlConnection sqlcon = new SqlConnection("Data Source=192.168.80.242;user id=oa;password=oapassword;initial catalog=test")) { sqlcon.Open(); using (SqlBulkCopy bcp = new SqlBulkCopy(sqlcon)) { bcp.BulkCopyTimeout = 3000; bcp.DestinationTableName = "dbo.Test01"; bcp.ColumnMappings.Add("id", "id"); bcp.ColumnMappings.Add("name1", "name1"); bcp.ColumnMappings.Add("name2", "name2"); bcp.ColumnMappings.Add("name3", "name3"); //映射到不同名列 bcp.ColumnMappings.Add("changedname4", "name4"); bcp.WriteToServer(dt); sqlcon.Close(); } }

  但是SqlBulkCopy使用时要注意以下几点:

  1   确认确实需要大容量更新在执行此操作,(几十行的数据请尽量使用别的渠道把). 

  2    确认数据一致性,与检查机制,以免遇到主键冲突,数据不符格式等意外。 

  3   SqlBulkCopy操作可能会导致对目标表元数据的更改(例如,禁用约束检查时)。如果出现这种情况,访问大容量插入表的并发快照隔离事务将失败。

  4   SqlBulkCopy将向数据库下大容量更新锁,请注意并发性,以免其他连接因长时间等待而超时。

 

 

五:DataSet的性能提升

  对于开发人员来说,ADO.NET 2.0最激动人心的变化莫过于.net开发组终于实现了他们许诺多年的事情:确实提升Dataset的性能了。

  由于1.1版本Dataset令人不敢恭维的性能使得Dataset许多方面被其性能问题而掩盖。

  现在,在大幅度提升了Dataset的性能后。Dataset终于能日趋完美了。


  提升是多方面的,被提升的方面包括下面几块:

  1. 索引引擎被大大的提升
 
  在对ADO.NET 2.0的Dataset作了相当数目的测试后,微软终于宣布Dataset2.0的数据访问能力获得极大的提高,广泛的数字是增加44倍!!!而且不像1.1中排序的陡峭曲线,2.0中的排序尽量做到了线型递增!

  真不知道.net小组是以前做的实在太烂了,还是有了新的狠招被发明了。^_^


  2. 二进序列制化的Dataset

  Dataset有个好属性是支持序列化,但是有很多人对其提出了批评。不是这些人不喜欢Dataset支持序列化,而是序列化后的Dataset真的是太肥了。大量的

  <xs:element name=””,type…占用了大量的空间以至让人难以忍受。


  现在我们只需要设置 RemotingFormat 属性为SerializationFormat.Binary(默认是SerializationFormat.XML),则在序列化时完全采用二进制的数据格式,如此数据较小,因而较有效率。(官方的观点是缩为SerializationFormat.XML的1/4)。

 

六:DataTable和其他方面的性能提升

  另一个好消息是DataTable的功能被大大增强了。毕竟我们不是时时需要Dataset。

  1.装载XML数据

  在 ADO.NET 1.* 时,离线的数据访问模型以 DataSet 对象为主,因此若要将 XML 的数据装载到 DataTable,必须通过 DataSet 来实现。若我们仅操作一个数据表,不需要访问多个数据表,则还需经过 DataSet 类才能赋予 DataTable 数据或将数据输出成 XML

  文件,其过程有点繁琐。ADO.NET 2.0 的 DataTable 类则新增了与 DataSet 相同的

  ReadXML、ReadXMLSchema、WriteXML 以及WriteXMLSchema 等方法。因此我们可以直接操作 DataTable 实例,而不需先创建 DataSet 类的实例来赋予 DataTable 实例数据,然后只使用 DataTable 实例。



  2.结果集直接装载DataTable

  可以通过DataTableReader对象生成DataTable和DataSet。利用DataTable和DataSet在2.0版本中新引入的方法Load,可以传递DataTableReader或者任何实现IDataReader接口的类对象。下面的代码就是通过Load方法将dt1的数据传递到新的数据表dt2中:

DataTableReader dtRdr = dt1.CreateDataReader(); DataTable dt2 = new DataTable(); dt2.Load(dtRdr);

  在使用Load方法装载多行数据时,可以先调用BeginLoadData方法来避免通知 (notifications),索引维护(index maintenance)以及约束检查(constraint checking),然后再通过EndLoadData方法返回数据。



  3.通过 SqlDataAdapter 类实例将 DataTable 内的记录更新回数据源
 
  现在我们还可以使用 SqlDataAdapter 类实例通过 DataGridView 更新的记录。

  Update方法由以下几个重载:

public int Update(DataRow[] dataRows); public override int Update(DataSet dataSet); public int Update(DataTable dataTable); public int Update(DataSet dataSet, string srcTable);

  4.千呼万唤的Merge方法,终于实现了。

public void Merge(DataTable table); public void Merge(DataTable table, bool preserveChanges); public void Merge(DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction);

  preserveChanges:参数决定当前 DataTable在相同主键记录合并时,是保留数据表内当前更新过的记录(设置为 True),还是以合并来的记录覆盖掉曾经修改过的记录(设置为 False)。

  5.轻量级对象和快速遍历

  ADO.NET 2.0中的DataTable提供了CreateDataReader方法(在之前的版本名为GetDataReader),该方法将创建一个 DataTableReader对象。DataTableReader与DataTable不同,它是一个轻量级的对象,其支持 Disconnected,这一点与DataReader(SqlDataReader)不同。这些特点决定遍历DataTableReader对象将更加快速,占用的数据资源更少(Disconnected)。下面的代码创建了一个DataTableReader对象,并将其绑定到 DataGridView控件上:

using (SqlConnection cn = new SqlConnection(cnStr)) { SqlCommand cmd = new SqlCommand(sqlAllCustomers, cn); SqlDataAdapter adpt = new SqlDataAdapter(cmd); DataTable dtCustomers = new DataTable("Customers"); adpt.Fill(dtCustomers); DataTableReader dtRdr = ds.CreateDataReader(); dgvCustomers.DataSource = dtRdr; }

 

posted @ 2008-08-13 11:28 Liu Jian 阅读(170) 评论(0) 编辑

ASP.NET Control Builders

 

By Dino Esposito

 

When you author an ASP.NET Web page you use a special markup language to identify constituent controls. The same markup language is employed by Visual Studio when you drag and drop controls from the toolbox onto a Web form. When you save the project, Visual Studio generates a markup script to describe the controls in the page and their settings. As a result, the final ASP.NET page is saved as a text file with the popular .aspx extension. When a request for the .aspx resource arrives, ASP.NET processes the source code of the .aspx file to build a page class dynamically.

The markup provides a description of the control; the ASP.NET parser is responsible for interpreting that markup and generating proper C# or Visual Basic .NET code. Are there any rules to guide the behavior of the parser? How can the parser know about the intended behavior of a particular markup element?

Each and every ASP.NET server control is associated with a control builder class. A control builder works side by side with the page parser and helps to analyze the markup for the control and to build all the necessary child controls. In this article, I’ll review the typical behavior of a control builder and discuss how to define a custom control builder for a custom server control.

 

The ControlBuilder Class

The control builder class handles any child markup element that the main tag of a control contains. The base class for all ASP.NET control builders is ControlBuilder. What’s the typical behavior of a control builder?

The ControlBuilder class parses any top-level block of markup that is flagged with the runat attribute. The builder processes every nested element it encounters within the control’s tags and adds a child control to the Controls collection of the control. In addition, it creates literal controls for the text located between nested control tags.

Most built-in server controls have their own control builders; for custom controls the default control builder works just fine (except in a few circumstances). You’ll use a custom control builder if the control you’re authoring has a complex layout or contains child tags that require ad hoc parsing.

Note, though, that as a control developer, you have other tools to partially govern the parsing process of the control’s markup. Let’s tackle this point first.

Parsing Attributes

If you derive a custom control from an existing control, you typically stick to the existing builder and don’t change the markup layout. As a result, either your control doesn’t have child elements, or any child elements are taken care of by the default builder. If you create your control from an abstract class, such as WebControl or CompositeControl, you can make yourself responsible for the control markup. When do you need to add child elements to a control’s markup? Style and collection properties may require a child tag. The default control builder, though, can take care of these situations with a little help from a couple of attributes: ParseChildren and PersistenceMode.

 

The ParseChildren attribute tells the ASP.NET parser and control builder how to parse the nested content of a control. The attribute takes a Boolean value that indicates whether the nested content should be parsed as properties or child controls.

 

The PersistenceMode attribute indicates how a control property is persisted declaratively in a host page. Figure 1 lists possible modes of persistence. The most common setting is InnerProperty, which instructs Visual Studio to save the contents of the property as a nested tag named after the property:

 

<x:MyControl runat="server" ... >

 <MyProperty>

   :

 </MyProperty>

</x:MyControl>

 

Property

Description

Attribute

The property persists as an encoded HTML attribute in the final markup.

EncodedInnerDefaultProperty

The property persists as the only inner text of the control. The property value is HTML encoded. Only a string can be given this designation.

InnerDefaultProperty

The property persists in the control as inner text and is the element’s default property. Only one property can be designated the default property.

InnerProperty

The property persists in the control as a nested tag. This is commonly used for complex objects with templates and styles.

Figure 1: Persistence modes for control properties.

If you choose InnerDefaultProperty, you can have only one nested tag; by opting for InnerProperty, you can have as many nested tags as needed. This is good for rich controls with multiple templates and styles.

You need a control builder when you need to take full control of the contents inside the opening and closing tag of the control.

Designing a Control Builder

The control builder class is automatically replaced when you apply the ControlBuilder attribute to a custom control, as follows:


[ControlBuilder(typeof(MyControlBuilder))]
public class MyControl
{
   :
}

 

The MyControlBuilder class kicks in when the ASP.NET parser gets to process the markup of any instance of MyControl. MyControlBuilder makes itself responsible for any control tree that results from the parsing process.

To understand the role of the control builder, let’s consider the markup for a list control, such as DropDownList:

 

<asp:dropdownlist runat="server">
  
<asp:listitem  />
  
<asp:listitem  />
  
<asp:listitem  />
  :
</asp:dropdownlist>

The <asp:ListItem> element indicates an object of type ListItem that is parsed to populate the Items collection of the DropDownList. A control builder determines the type of the object behind the <asp:ListItem> tag and how the information it may contain is stored inside the control.

 

Let’s consider the possible markup of a custom list control, such as a TextBoxList control — a control that renders out a table with a column for the label and a column for the textbox:



<x:textboxlist runat="server">
  
<x:inputfield  />
  
<x:inputfield  />
  
<x:inputfield  />
  :
</x:textboxlist>

To implement a control in accordance with the schema just mentioned, three classes are needed: the TextBoxList class for the control, the InputField class for the child element, and the control builder class to control the page parser.

The Custom Control Builder Class

The control builder class is rarely a very complex piece of code. Its structure is extremely simple and agile and basically consists of a series of overrides. The only method you absolutely need to override for a significant and functional implementation is GetChildControlType.

The GetChildControlType method returns the type of the control’s children tags. The default implementation of the base class simply returns null. The method takes two arguments: the name of the child tag found, and its collection of attributes. What programmers should do to implement the method depends mostly on the schema they have in mind. The method is responsible for getting the type that a particular child tag represents. If you need to map the nested markup to some custom structures, the GetChildControlType method is crucial.

In the sample control builder, the GetChildControlType method should take into account any tag named <InputField> and force the runtime to create an instance of the InputField type (see Figure 2).

 

public class TextBoxListControlBuilder : ControlBuilder
{
   
public override Type GetChildControlType(
       
string tagName,
       IDictionary attributes)
   
{
       
if (tagName.ToLower() == "inputfield")
           
return typeof(InputField);
       
return null;
   }

}


Figure 2: GetChildControlType should take into account any tag named <InputField> and force the runtime to create an instance of the InputField type.

With a similar implementation, only recognized tags are processed in a custom way. All other tags will be treated by the page parser as literal markup and converted to literal controls, which are added to the control’s child control tree. The TextBoxList control features an internal array of InputField instances (see Figure 3).


  
[ControlBuilder(
typeof(TextBoxListControlBuilder))]
[ParseChildren(
false)]
public class TextBoxList : WebControl
{
   
private ArrayList m_formFields = new ArrayList();
   
public ArrayList Items {
       
get {return m_formFields;}
   }

   :
}

Figure 3: TextBoxList features an internal array of InputField instances.

 

The ControlBuilder attribute indicates the type of the control builder that must be used for this control. The ParseChildren attribute explicitly states that the general rule that child tags map to properties is not true in this case; parsing still occurs, but it is taken care of by the custom builder. The control also needs an additional override — the AddParsedSubObject method:

 

protected override void AddParsedSubObject(object obj)
{
   
if (obj is ProAspNet.CS.Ch20.FormField)
       m_formFields.Add(obj);
}

Fundamental is the role played by the AddParsedSubObject method. Any tag that the GetChildControlType method recognizes is transformed into a living instance of the specified type. This object is then passed to the AddParsedSubObject method for further processing. If the object is of the correct type, it’s added to the internal collection of InputField objects. At this point, once the InputField class is defined, the control is ready for rendering.

 

The InputField Class

The InputField class is a class that gathers information about the textboxes to create within the main control. The class features a couple of string properties named Label and Text. The Label property indicates the text for the label; the Text property indicates the default text for the textbox. Figure 4 shows the full source code of the class. The physical textbox is created when the control renders out. Other properties in the control’s programming interface can let page authors access the contents of the child textboxes.

public class InputField
{
   
private string _label;
   
private string _text;

   
public InputField()
   
{
   }

   
public string Label
   
{
       
get return _label; }
       
set { _label = value; }
   }

   
public string Text
   
{
       
get return _text; }
       
set { _text = value; }
   }

}

Figure 4: The InputField class.

The TextBoxList control will typically be a composite control, and as such will render its contents through the CreateChildControls method. Internally, the method loops through the _inputFields collection and creates as many labels and textboxes as necessary.

In the end, the goal of the control builder is to help the parser understand the contents of the control’s markup and to load the proper information inside the control instance bound to the page. If you have a made-to-measure control builder, you can design at will the markup of the control. The PersistenceMode attribute assigned to control properties helps Visual Studio persist correctly the markup of the control when you use it in the Web Forms designer.

Conclusion

Most ASP.NET built-in controls have their own builder, so there’s no need for you to change or replace it. Custom controls may constitute a different story. If the custom control is designed after an existing control, you normally have no reason to replace the builder. If you want to design a completely custom control and go for a rich and descriptive markup, then use a control builder.

 

Dino Esposito is a Solid Quality Learning mentor and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Join the blog at http://weblogs.asp.net/despos.

posted @ 2008-07-17 17:33 Liu Jian 阅读(633) 评论(0) 编辑
摘要: WebForm最大的魅力大概就是它自己的一套事件处理机制了,要做一个好的Control,必须深入理解这套机制,只有这样才可以让我们的Control有一整套Professional的Event,而IPostBackDataHandler和IPostBackEventHandler是实现事件机制的核心接口,在我的上一篇文章(Web Control 开发系列(二) 深入解析Page的PostBack过程...阅读全文
posted @ 2008-07-10 14:35 Liu Jian 阅读(2803) 评论(11) 编辑
摘要: IPostBackDataHandler和IPostBackEventHandler对于实现一个WebControl是非常重要的,如果你的 Contro仅仅是readonly的,也就是说不会让客户端进行输入和修改,那么这两个接口就没有用,一旦你要和客户端交互,那么这两个接口是必须掌握的。IPostBackDataHandler可以让你的Control和客户端的输入数据进行交互,比如TextBox,...阅读全文
posted @ 2008-07-09 09:25 Liu Jian 阅读(2997) 评论(18) 编辑
摘要: Page是WebForm编程基本元素,它从TemplateControl派生,而TemplateControl又从Control派生,所以Page实际就是一个Control。同时Page也实现了IHttpHandler接口,所以它可以接受Http请求,进行处理。可以认为一个Page是由很多的Control按照树形结构组织的,而树的根就是Page(一个实现了IHttphandler的Control)...阅读全文
posted @ 2008-07-03 10:47 Liu Jian 阅读(2929) 评论(19) 编辑
摘要: 由于工作的关系,我开始开发一个商业的Web版数据绑定控件,在此之前,我已经从事了4年的.net WinForm 控件的开发,经过最近一段时间的研究和实践,我认为要开发一款专业的Web控件,必须深入的了解微软的整套的WebForm框架和相关基础控件的实现。所以我决定把我学习到的东西,写一个系列的文章,这些文章的顺序我会在整个系列写完后做一个整理。一方面可以方便对这个领域感兴趣的朋友,另一方面也便于我...阅读全文
posted @ 2008-07-02 10:07 Liu Jian 阅读(2485) 评论(22) 编辑
仅列出标题  下一页