随笔-70  评论-2  文章-0  trackbacks-0
  2008年9月28日
     元数据简介

  元数据 (metadata) 最常见的定义为“有关数据的结构数据”,或者再简单一点就是“关于数据的信息”,日常生活中的图例、图书馆目录卡和名片等都可以看作是元数据。在关系型数据库管理系统 (DBMS) 中,元数据描述了数据的结构和意义。比如在管理、维护 SQL Server 或者是开发数据库应用程序的时候,我们经常要获取一些涉及到数据库架构的信息:

  •   某个数据库中的表和视图的个数以及名称;
  •   某个表或者视图中列的个数以及每一列的名称、数据类型、长度、精度、描述等;
  •   某个表上定义的约束;
  •   某个表上定义的索引以及主键/外键的信息。

  下面我们将介绍几种获取元数据的方法。

  获取元数据

  使用系统存储过程与系统函数访问元数据

  获取元数据最常用的方法是使用 SQL Server 提供的系统存储过程与系统函数。

  系统存储过程与系统函数在系统表和元数据之间提供了一个抽象层,使得我们不用直接查询系统表就能获得当前数据库对象的元数据。

  常用的与元数据有关的系统存储过程有以下一些:

  系统存储过程

  •   sp_columns 返回指定表或视图的列的详细信息。
  •   sp_databases 返回当前服务器上的所有数据库的基本信息。
  •   sp_fkeys 若参数为带有主键的表,则返回包含指向该表的外键的所有表;若参数为带有外键的表名,则返回所有同过主键/外键关系与该外键相关联的所有表。
  •   sp_pkeys 返回指定表的主键信息。
  •   sp_server_info 返回当前服务器的各种特性及其对应取值。
  •   sp_sproc_columns 返回指定存储过程的的输入、输出参数的信息。
  •   sp_statistics 返回指定的表或索引视图上的所有索引以及统计的信息。
  •   sp_stored_procedures 返回当前数据库的存储过程列表,包含系统存储过程。
  •   sp_tables 返回当前数据库的所有表和视图,包含系统表。

  常用的与元数据有关的系统函数有以下一些:

  系统函数

  •   COLUMNPROPERTY 返回有关列或过程参数的信息,如是否允许空值,是否为计算列等。
  •   COL_LENGTH 返回指定数据库的指定属性值,如是否处于只读模式等。
  •   DATABASEPROPERTYEX 返回指定数据库的指定选项或属性的当前设置,如数据库的状态、恢复模型等。
  •   OBJECT_ID 返回指定数据库对象名的标识号
  •   OBJECT_NAME 返回指定数据库对象标识号的对象名。
  •   OBJECTPROPERTY 返回指定数据库对象标识号的有关信息,如是否为表,是否为约束等。
  •   fn_listextendedproperty 返回数据库对象的扩展属性值,如对象描述、格式规则、输入掩码等。

  由于我们无法直接利用到存储过程与函数的返回结果,因此只有在我们关心的只是查询的结果,而不需要进一步利用这些结果的时候,我们会使用系统存储过程与系统函数来查询元数据。

例如,如果要获得当前服务器上所有数据库的基本信息,我们可以在查询分析器里面运行:

  EXEC sp_databases
  GO

  在返回结果中我们可以看到数据库的名称、大小及备注等信息。

  但是如果要引用这部分信息,或者存储这部分信息以供后面使用,那么我们必须借助中间表来完成这个操作:

  CREATE TABLE #sp_result
  (
  DATABASE_NAME sysname,
  DATABASE_SIZE int,
  REMARKS varchar(254) NULL
  )
  GO
  INSERT INTO #sp_result
  EXEC ('sp_databases')
  GO

  使用信息架构视图访问元数据

  信息架构视图基于 SQL-92 标准中针对架构视图的定义,这些视图独立于系统表,提供了关于 SQL Server 元数据的内部视图。信息架构视图的最大优点是,即使我们对系统表进行了重要的修改,应用程序也可以正常地使用这些视图进行访问。因此对于应用程序来说,只要是符合 SQL-92 标准的数据库系统,使用信息架构视图总是可以正常工作的。

  信息架构视图

  •   INFORMATION_SCHEMA.CHECK_CONSTRAINTS:返回有关列或过程参数的信息,如是否允许空值,是否为计算列等。
  •   INFORMATION_SCHEMA.COLUMNS:返回当前数据库中当前用户可以访问的所有列及其基本信息。
  •   INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE:返回当前数据库中定义了约束的所有列及其约束名。
  •   INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE:返回当前数据库中定义了约束的所有表及其约束名。
  •   INFORMATION_SCHEMA.KEY_COLUMN_USAGE:返回当前数据库中作为主键/外键约束的所有列。
  •   INFORMATION_SCHEMA.SCHEMATA:返回当前用户具有权限的所有数据库及其基本信息。
  •   INFORMATION_SCHEMA.TABLES:返回当前用户具有权限的当前数据库中的所有表或者视图及其基本信息。
  •   INFORMATION_SCHEMA.VIEWS:返回当前数据库中的当前用户可以访问的视图及其所有者、定义等信息。

  由于这些信息架构都是以视图的方式存在的,因此我们可以很方便地获得并利用需要的信息。

  例如,我们要得到某个表有多少列,可以使用以下语句:

  SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
  WHERE TABLE_NAME='mytable'

  使用系统表访问元数据

  虽然使用系统存储过程、系统函数与信息架构视图已经可以为我们提供了相当丰富的元数据信息,但是对于某些特殊的元数据信息,我们仍然需要直接对系统表进行查询。因为SQL Server 将所有数据库对象的信息均存放在系统表中,作为 SQL Server 的管理、开发人员,了解各个系统表的作用将有助于我们了解 SQL Server 的内在工作原理。

  SQL Server 的系统表非常多,其中最常用的与元数据查询有关的表有如下一些:

  •   syscolumns 存储每个表和视图中的每一列的信息以及存储过程中的每个参数的信息。
  •   syscomments 存储包含每个视图、规则、默认值、触发器、CHECK 约束、DEFAULT 约束和存储过程的原始 SQL 文本语句。
  •   sysconstraints 存储当前数据库中每一个约束的基本信息。
  •   sysdatabases 存储当前服务器上每一个数据库的基本信息。
  •   sysindexes 存储当前数据库中的每个索引的信息。
  •   sysobjects 存储数据库内的每个对象(约束、默认值、日志、规则、存储过程等)的基本信息。
  •   sysreferences 存储所有包括 FOREIGN KEY 约束的列。
  •   systypes 存储系统提供的每种数据类型和用户定义数据类型的详细信息。

  将系统存储过程、系统函数、信息架构视图与系统表结合使用,可以方便地让我们获得所有需要的元数据信息。

示例:

  1、 获得当前数据库所有用户表的名称。

  SELECT OBJECT_NAME (id)
  FROM sysobjects
  WHERE xtype = 'U' AND OBJECTPROPERTY (id, 'IsMSShipped') = 0

  其中主要用到了系统表 sysobjects以及其属性 xtype,还有就是用到了 OBJECTPROPERTY 系统函数来判断是不是安装 SQL Server 的过程中创建的对象。

  2、获得指定表上所有的索引名称。

  SELECT name FROM sysindexes
  WHERE id = OBJECT_ID ('mytable') AND indid > 0

  综合实例

  下面给出了一个存储过程,它的作用是自动将当前数据库的用户存储过程加密。

  DECLARE @sp_name nvarchar(400)
  DECLARE @sp_content nvarchar(2000)
  DECLARE @asbegin int
  declare @now datetime
  select @now = getdate()
  DECLARE sp_cursor CURSOR FOR
  SELECT object_name(id)
  FROM sysobjects
  WHERE xtype = 'P'
  AND type = 'P'
  AND crdate < @now
  AND OBJECTPROPERTY(id, 'IsMSShipped')=0
  OPEN sp_cursor
  FETCH NEXT FROM sp_cursor
  INTO @sp_name
  WHILE @@FETCH_STATUS = 0
  BEGIN
  SELECT @sp_content = text FROM
  syscomments WHERE id = OBJECT_ID(@sp_name)
  SELECT @asbegin =
  PATINDEX ( '%AS' + char(13) + '%', @sp_content)
  SELECT @sp_content =
  SUBSTRING(@sp_content, 1, @asbegin - 1)
  + ' WITH ENCRYPTION AS'
  + SUBSTRING (@sp_content, @asbegin+2, LEN(@sp_content))
  SELECT @sp_name = 'DROP PROCEDURE [' + @sp_name + ']'
  EXEC sp_executesql @sp_name
  EXEC sp_executesql @sp_content
  FETCH NEXT FROM sp_cursor
  INTO @sp_name
  END
  CLOSE sp_cursor
  DEALLOCATE sp_cursor

  该存储过程利用了 sysobjects 和 syscomments 表,并巧妙地修改了原存储过程的 SQL 定义语句,将 AS 修改为了 WITH ENCRYPTION AS,从而达到了加密存储过程的目的。

posted @ 2008-09-28 12:38 bluealarm| 编辑
     摘要: sysaltfiles 主数据库 保存数据库的文件 syscharsets 主数据库字符集与排序顺序sysconfigures 主数据库 配置选项syscurconfigs 主数据库当前配置选项 sysdatabases 主数据库服务器中的数据库 syslanguages 主数据库语言 syslogins 主数据库 登陆帐号信息sysoledbusers 主数据库 链接服务器登陆信息 syspro... 阅读全文
posted @ 2008-09-28 10:24 bluealarm| 编辑

在SQL中可以通过查询Master下的系统表(sys)和系统视图(information_schema)获取数据库的信息。SQL2000和SQL2005的结构略有不同。

     系统表结构参考系统表详细说明。

     系统信息结构图参考:http://dev.mysql.com/doc/refman/5.1/zh/information-schema.html

1、2000下操作:

系统表目录:大部分以dbo.sys为前缀。

系统视图目录:有20个常用的视图,以INFORMATION_SCHEMA为前缀。

在2000中我们可以用这两种方式的查询来得到相同的效果。

如:查询所有数据库:

select name from master..sysdatabases

select  catalog_name   from   INFORMATION_SCHEMA.SCHEMATA

效果一样。

查询用户创建的所有数据库

select * from master..sysdatabases D where sid not in(select sid from master..syslogins where name='sa')

或者

     select dbid, name AS DB_NAME from master..sysdatabases  where sid <> 0x01

     或者

    select name from master..sysdatabases order by name asc

 

     获取当前数据库中的所有用户表:

select Name from sysobjects where xtype='u' and status>=0

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'

获取某一个表的所有字段

select name from syscolumns where id=object_id('表名')

SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '表名'

查看与某一个表相关的视图、存储过程、函数

select a.* from sysobjects a, syscomments b where a.id = b.id and b.text like '%表名%'

查看当前数据库中所有存储过程

select name as 存储过程名称 from sysobjects where xtype='P'

查询某一个表的字段和数据类型

select name,xtye from syscolumns where id=object_id('表名')

SELECT COLUMN_NAME,DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '表名'

2、2005下操作:

系统表目录:2000中的系统表,在05中都放在了系统视图目录下。

系统视图目录:存放了系统表和视图,增加了许多新的系统表,如支持xml。以sys.为前缀。

 在2005中,系统表仍然属于Master数据库下。但是视图却被分配到了各个数据库下。因此:

select name from master..sysdatabases

select  catalog_name   from   INFORMATION_SCHEMA.SCHEMATA

上面的两种查询就会有不同的结果。

第一个查询仍然可以返回所有的数据库列表。

而第二个查询只返回当前连接的数据库的信息。

除此之外的其他针对具体数据库的操作,跟2000一样。

     由于结构不同,为了保证统一性,我们在对整个服务器操作时,最好使用系统表。而在对某一个具体的数据库操作时,则既可以使用系统表也可以使用信息结构图。

posted @ 2008-09-28 10:17 bluealarm| 编辑
  2008年9月27日

     WCF能够托管CLR类型,并将他们公开为服务,也能够以本地CLR的方式来使用服务。WCF服务的操作接收和返回CLR的类型,WCF客户端则传递和处理返回的CLR类型。CLR类型是.net的概念。由于面向服务的一个核心原则就是在跨越服务边界时,服务不能够暴露他们的实现技术。这就意味着WCF不允许在跨越服务边界是公开CLR的数据类型。因此,需要找到一种方法,实现CLR数据类型和标准的平台无关的表示形式之间的转换。这样的表示形式是基于XML的样式或信息集(Infoset)。实现这两者之间的转换的方法就是数据契约(DataContract)。

.NET序列化

     在本地使用的对象和引用等都属于CLR的概念,无法实现与WCF服务操作之间的传递。解决办法就是将对象封送(Marshaling)其它的平台和技术。在将对象作为参数进行传递时,需要发送的是对象的状态,然后接收端再将它转化为本地的表示形式 。这种传递状态的方式称为按值封送(Marshaling by value)。执行按值封送的最简单办法是利用大多数平台(包括.net)自身提供的序列化技术。

     .NET通过反射技术自动的实现了对象的序列化和反序列化。在实现中,是否执行序列化应该有类的开发者决定。.NET通过Serializable属性来支持类的序列化。如果类中含有非序列化成员,通常情况下,非序列化成员就是那些需要执行特殊的初始化操作的引用类型。解决这个问题的办法是为这样的成员标记NonSerialized特性,然后在反序列化时,采取定制步骤对它们进行初始化。

格式器

     .NET为类型的序列化和反序列化提供了两种格式器。BinaryFormatter会将类型序列化为二进制格式。由于不需要解析,这种格式器能够快速的执行序列化和反序列化操作。SoapFormatter则使用了.NET特定的SOAPXML格式,在序列化时,它引入了组合格式,然后在反序列化时对组合格式进行解析,完成转换。.NET格式器并不能满足面向服务的交互。因此WCF提供了自己的格式器DataContractSerializier。它能够共享数据契约而不是基本的类型信息。

序列化数据契约

     当一个服务操作接收和返回任意类型和参数时,WCF会使用DataContractSerializier对参数进行序列化和反序列化。这意味着只要各参与方拥有相同的数据契约的定义,我们就可以将可序列化类型作为参数或返回值进行传递。

     所有的.NET内建的基本类型都是可序列化的。如果需要在操作中使用定制的类型参数,需要满足两个条件:首先,类型必须是可序列化的(Serializable)。其次,客户端与服务都拥有该类型的本地定义,而且该类型应该具有相同的数据样式。

     Serializable所指代的涵义是类型的所有成员都是可序列化的。更好的方式是能够提供一种明确参与(Opt-In)的途径,只有那些契约的开发这明确包含的成员才应该放到数据契约中。

     Serializable有以下缺点:他无法实现类型的类型的服务特性(具有成为WCF操作参数的能力)与序列化能力之间的职责分离。不支持类型名和成员名的别名。无法将一个新类型映射为预定义的数据契约。它直接操作成员字段,破坏了属性的封装性。没有直接支持版本控制。

     针对Serializable的缺点,WCF提供了DataContract和DataMember特性。DataContract应用到类级,DataMember应用到成员级。

客户端导入数据契约

     当客户端导入数据契约的定义时,它使用的是与服务端等效的定义,但不是同一个数据契约。导入的定义会保留类或结构原来的类型名。两端使用的数据契约可以是等效的而不是同一个。当然也可以使用不等效的,客户端对它自己的契约的控制和配置,与服务无关。

     在导入定义中,服务端原来的私有字段或属性在客户端都被定义成了共有属性。

     如果将DataMember应用到属性上,该属性必须具有get和set访问器。否则会抛出InvalidDataContractException异常。不要将DataMember同时应用到字段和属性上,会导致重复定义。

数据契约的等效性

      如果两个数据契约具有相同的传输型表示形式,就可以认为是等效的。等效的数据契约包括:类型定义相同,或者指向两个不同类型的数据契约,他们的契约名以及成员的名称完全相同。等效的数据契约可以互换,因为WCF允许服务操作与它的数据契约等效的数据契约。

     定义一个等效数据契约的最常用方法是使用DataContract和DataMember的Name属性,将数据契约映射为另一个数据契约。

枚举与数据契约

枚举类型的定义总是支持序列化的,因此,不必应用DataContract特性。数据契约隐式的包含了枚举对象的所有值。如果需要将枚举的特定值排除在外。则需要显示使用DataContract和EnumMember特性。如:

Code

委托与数据契约

     在序列化一个包含了委托成员变量的对象时,委托的内部调用列表也会被序列化。列表中的结构是本地的,而且我们也不能保证列表中的对象都是可序列化的。这回导致序列化操作时失败。因此,如果数据契约时可序列化的。应该将委托排除在外,不使用DataMember属性。

数据集(表)与数据契约

     在.NET中,与数据库本地的交互是通过ADO.NET的数据集或数据表类型。DataSet和DataTable类型是可序列化的,因为他们的定义标记了Serializable属性。我们可以定义有效的服务契约,接收或返回数据集(表)。但是数据行(row)不是序列化的,因此不能直接作为参数。

     使用数据集(表)样式复杂,而且可能暴露内部的数据结构,同样对数据库样式的修改会影响到客户端。所以更好的做法是暴露数据的操作而非数据本身。如果要传递数据本身,最好的方法是使用与数据结构无关的类型,如数组。

泛型

     不要定义包含了泛型类型参数的数据契约。泛型是.NET的特定技术,使用他们可能会与WCF的面向服务端本质发生冲突。但是,我们仍然可以在数据契约中使用限定的泛型类型,只要在服务契约中指定了类型参数,并且指定的类型参数具有有效的数据类型。如:

Code

 

集合

     在.NET中,所有类型的集合都实现了IEnumerable或IEnumerable<T>接口。一个数据契约的数据成员可以是一个集合类型,服务契约也可以定义直接与集合交互的操作。因为.NET集合是.NET特有的,所以WCF不能在服务中公开他们,WCF专门为集合提供了封送原则。在服务中,不管使用那一种结合接口,它的传输形式都使用了数组。如:

Code

导出结果为:

Code

      在.NET3.5后,我们可以通过手动或者使用服务引用向导来实现客户端映射样式的选择。

     可以在Add Service Reference 时选择Advanced界面,弹出Service Reference Setting界面。可以在该界面中选择映射的type。

 

posted @ 2008-09-27 15:26 bluealarm| 编辑

      .NET 提供了三种Timer:

基于服务的Timer   System.Timers.Timer
基于线程的Timer   System.Threading.Timer 
基于窗体的Timer   System.Windows.Forms.Timer

其中Windows Timers只是提供了和WinAPI 一样的Timer,仍然是基于消息,是单线程的。其它两个是基于线程池的Thread Pool,这样产生的时间间隔准确均匀。

单线程Timer问题

  Win32 API中有个SetTimer函数,可以为一个窗口创建一个定时器,这个定时器会定时产生消息WM_TIMER也可以调用指定的回调函数,但是是单线程的。

单线程的定时器会有很多问题,首先是不准时,定时器只是定时把消息WM_TIMER访到线程的消息队列里,但是并不保证消息会立刻被响应,如果碰巧系统比较忙,那么消息可能会在队列里放一端时间才被响应,还会造成本来应该间隔一段时间发生的消息响应连续发生。

解决方法通常是

OnTimer(...)
{
 //Timer process.....
 
 MSG msg;
 While(PeekMessage(&msg, m_hWnd, WM_TIMER, WM_TIMER, PM_REMOVE));
}
在当前Timer处理中,把消息队列里的WM_TIMER消息,清除掉。但是如果你不去调用GetMessage,那么就不会有Timer发生了。
 多线程Timer重入问题

由于使用多线程定时器,就会出现如果一个Timer处理没有完成,到了时间下一个照样会发生,这就会导致重入。

对付重入问题通常的办法是加锁,但是对于 Timer却不能简单的这样做,你需要评估一下。

首先Timer处理里本来就不应该做太需要时间的事情,或者花费时间无法估计的事情,比同远方的服务器建立一个网络连接,这样的做法尽量避免。

如果实在无法避免,那么要评估Timer处理超时是否经常发生,如果是很少出现,那么可以用lock(Object)的方法来防止重入。
如果这种情况经常出现呢?那就要用另外的方法来防止重入。

可以设置一个标志,表示一个Timer处理正在执行,下一个Timer发生的时候发现上一个没有执行完就放弃执行。

static  int Finished= 0;
public static void threadTimerCallback(Object obj)
{
     if ( Finished== 0 )
    {
         Finished= 1;         

         Thread.Sleep(2000);

         Finished= 0;
      }
}

但是在多线程下给inTimer赋值不够安全。

Interlocked.Exchange提供了一种轻量级的线程安全的给对象赋值的方法。

 static int Finished= 0;
 public static void threadTimerCallback(Object obj)
 {
       if ( Interlocked.Exchange(ref Finished, 1) == 0 )
      {
           Thread.Sleep(250);

           Interlocked.Exchange(ref Finished, 0);
      }
 }

posted @ 2008-09-27 12:21 bluealarm| 编辑
  2008年9月25日

     在使用Items类型的控件,如ListBox,ListView进行数据显示时,常常通过与数据源集合进行绑定。为了进行使变化同步,数据源要注意两点。假设要显示的是People类的集合。

     1、People类的成员必须读写属性。即将字段定义为private,然后定义读写属性。而不是将字段定义为public。

     2、为了实时变化,需要继承INotifyPropertyChanged。此接口实现属性变化通知功能。

    

 

posted @ 2008-09-25 16:05 bluealarm| 编辑

     数据模版(DataTemplate)常用来定制数据的显示方式。例如我们可以通过将时间的字符串形式表示为一个图片。DataTemplate与ControlTemplate有点相似。它们都是用FrameworkElementFactroy对象定义它们的视觉树属性。ControlTemplate是将视觉树的视觉属性绑定到控件属性(control property),而DataTemplate是将视觉树的视觉属性绑定到数据属性(Data Property)。DataTemplate和ControlTemplate都从FrameworkTemplate继承。

     数据模板适用于Content Control类控件与Items Control类控件。

     DataTemple有两个构造函数。在public DataTemplate(object dataType)构造函数中,datatype是此模板要应用的数据类对象,在code中可以使用typeof。在XAML中可以使用x:Type。

     下面是一个简单的数据类Country,显示国籍和国旗。

Code

下面如果我们想在一个ListBox中显示。就可以自定义ListBox的数据模板。

XAML方式

 

Code

 

说明:x:Key表示在资源字典中的Key值,Datatype表示该模板针对的数据类型是Country。

引用自定义名字空间:

xmlns:demo="clr-namespace:Demo" 

将此模板应用到ListBox中:

Code

 

Code方式

通过code的方式也可以实现DataTemplate。主要用到的是FramewrokElementFactroy来创建视觉元素。

如:

Code

 

     控件模板的使用与DataTemplate类似。

posted @ 2008-09-25 15:11 bluealarm| 编辑

参考文章:

http://developer.51cto.com/art/200805/74151.htm

http://developer.51cto.com/art/200805/74150.htm   

.NET上下文     

     每个新的应用程序域启动时都有一个单一的上下文,我们称为默认上下文。默认上下文根本不提供任何组件服务。它存在的主要原因是因为有助于保持一致的编程模式。在新应用程序域中创建的第一个对象将放置在默认上下文中,即使它不是一个面向上下文对象也如此。这样可保持所有对象都在上下文中执行这样一个设计原则,即使它们不关心组件服务。应用程序域可以包含多个上下文,并且.NET会根据需要创建新的上下文。对应用程序域可以包含的上下文数量没有限制。一个上下文只属于一个应用程序域,它可以承载多个面向上下文对象(见图11-1)。每个上下文都有一个称为上下文ID的唯一I D(整型),在应用程序域范围中它的值是唯一的。每个.NET上下文都有与之关联的一个上下文对象。上下文对象是Context类的一个实例,它在System.Runtime.Remoting.Contexts命名空间中定义。通常不需要与上下文对象进行交互。但为了诊断和跟踪,有时使用上下文对象的只读属性ContextID检索上下文ID比较有用。

public class Context

{

   public virtual int ContextID{get;}

   //其他成员

}

每个对象都可以访问正执行于其中的上下文对象,方法是通过Thread类的Current- Context静态只读属性:

public sealed class Thread

{

   public static Context CurrentContext{ get; }

   /* 其他成员 */

}

例如,下面是对象如何跟踪其上下文ID的方法:

int contextID = Thread.CurrentContext.ContextID;

Trace.WriteLine("Context ID is " + contextID);

请注意,线程能够进入和退出上下文,通常,它们跟任何上下文都没有关联。

将对象分配给上下文(Assigning Objects to Contexts)

     如前所述,有两种.NET类型:上下文敏捷和面向上下文类型。两种类型都始终在某个上下文中执行,主要的区别在于它们与该上下文的关系。上下文敏捷行为是.NET的默认行为。未从ContextBoundObject派生的任何类都是上下文敏捷的。上下文敏捷对象不关心具体组件服务;它们可以在其调用客户端的上下文中执行,因为.NET无须拦截进入它们的调用。当客户端创建上下文敏捷对象时,该对象将在其创建客户端的上下文中执行。客户端对该对象获得一个直接引用,并且不涉及任何代理。客户端可以将对象引用传递到同一上下文或不同上下文中的不同客户端。在其他客户端使用该对象时,对象将在该客户端的上下文中执行。图11-2显示了上下文敏捷模型。请注意:说一个上下文敏捷的对象没有上下文是不正确的。它其实有一个,即进行调用的客户端的上下文。如果上下文敏捷的对象检索其上下文对象,并查询上下文ID的值,它将获得与其调用客户端相同的上下文ID。

图11-2:上下文敏捷的对象

提示:COM+也有上下文敏捷对象的概念,其形式为集成自由线程封送器(FTM)的对象。当一个对象有其他 COM+ 对象作为成员时,FTM 带来了一些很麻烦的副作用,因此要避免这个技术。

     若是面向上下文对象,情况会显著不同。面向上下文对象在整个生命期间都绑定到某个特定的上下文中。具体对象会在哪个上下文中,这是在对象创建时基于其请求的服务及其创建之的客户端的上下文而决定的。如果创建它的客户端的上下文“足够满足”对象的需求,也就是说,上下文具有足够的属性,并且客户端和对象使用一组兼容的组件服务,则将把对象放置在创建它的客户端的上下文中。另一方面,如果对象需要某些其他服务而创建它的客户端上下文不支持的话,则.NET将创建一个新的上下文并在其中放置新的对象。请注意,.NET不会尝试在该应用程序域中查找是否已经有适合于对象的另一个上下文。算法非常简单:对象要么共享其创建者的上下文,要么获得一个新的上下文。此算法有意牺牲内存和上下文管理的成本,以加快将新对象分配到上下文的速度。另一个方法可以先检查可能很长的现有上下文列表中的每一个,但该搜索可能会花费较长时间从而影响性能。如果将对象放置在与创建它的客户端的上下文不同的上下文中,则客户端将从.NET获得一个代理,而不是直接引用(见图11-3)。代理将拦截客户端在对象上的调用,并执行某些调用前置处理和后置处理,以提供对象所需的服务。

图11-3:面向上下文对象的客户端通过代理对其进行访问

提示:将面向上下文对象分配到上下文中的.NET策略与COM+上下文激活策略非常相似。

调用拦截架构(The Call Interception Architecture)

     跨上下文拦截架构与跨应用程序域边界所使用的架构相似。回忆一下第10章,在.NET中代理有两部分:透明代理和真实代理。透明代理有跟对象相同的公共接口。当客户端调用透明代理时,它将把堆栈转换为消息,并将消息传递到真实代理。该消息是实现了 IMessage接口的对象:

public interface IMessage

{   

   IDictionary Properties{ get; }

}

该消息是一个属性集合,如方法名及其参数。真实代理知道实际对象的驻留地点。在跨应用程序域调用的情况下,真实代理需要使用一个格式器来序列化该消息,并将其传递到通道中。在跨上下文调用中,真实代理需要在转发调用给对象之前,实施各种拦截步骤。.NET的良好设计允许.NET在两种情况下使用相同的真实代理。真实代理并不知道格式器、通道或上下文拦截器;它只是将消息传递到消息接收器。消息接收器是实现IMessageSink接口的对象,IMessageSink定义在System.Runtime.Remoting.Messaging命名空间中。

public interface IMessageSink

{

   IMessageSink NextSink{ get; }

   IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink);

   IMessage SyncProcessMessage(IMessage msg);

}

.NET将消息接收器排列在一个链表中。每个消息接收器都知道列表中的下一个接收器(你可以通过NextSink属性获取下一个接收器)。真实代理将调用第一个接收器的SyncProcess-Message()方法,让它处理消息。处理完消息后,第一个接收器将调用下一个接收器的 SyncProcessMessage()。在跨应用程序域调用时,客户端上的第一个接收器是消息格式器(再次见图10-9)。在格式化消息后,格式接收器将把它传递到下一个接收器,即传输通道。SyncProcessMessage()方法返回到代理时,它返回对象返回的消息。

提示:IMessageSink接口也提供AsyncProcessMessage()方法,它可以拦截异步调用(此主题超出了本书的范围)。

跨上下文接收器

     在跨上下文调用中,无须格式器;.NET使用一个称为CrossContextChannel的内部通道,它也是一个消息接收器。但是,客户端和对象的组件服务配置之间有一些区别,要由接收器来弥补这些区别。.NET会在客户端上下文和对象之间安装所有需要的消息接收器(见图11-4)。

图11-4:对面向上下文对象的跨上下文调用

.NET上下文拦截架构与装饰(Decorator)设计模式(注1)相似,它是一个面向方面编程的特有情况(将在后面进行讨论,位于补充介绍“上下文、AOP 和 Indigo”中)。典型的消息接收器既执行调用前置处理,也执行调用后置处理。标准的例子就是线程同步。接收器需要在调用对象之前先获得锁,并且它在方法返回之后必须释放该锁。调用链上的下一个接收器可能会实现某种安全机制,等等。

最好用一个示例来说明接收器工作的方式。示例11-1显示了一个通用的接收器实现。接收器构造函数将接受链中的下一个接收器。调用SyncProcessMessage()方法时,接收器会执行某些调用前置处理,然后调用下一个接收器上的SyncProcessMessage()。调用将

沿着接收器链一直下去,直到达到一个堆栈生成器即最后一个接收器为止。堆栈生成器将把消息转换成堆栈框架,并调用对象。当调用返回到堆栈生成器时,它生成一条带有方法结果的返回消息,并将该消息返回到调用它的接收器。然后,该接收器会进行调用后置处理,并将控制权返回给调用它的接收器,等等。最后,调用将返回通用的接收器。现在,通用接收器可以检查返回的消息,并进行一些调用后置处理,然后将控制权返回给调用它的接收器。链中的第一个接收器将把控制权返回给真实代理,为之提供来自对象的返回消息。真实代理将把消息返回给透明代理,透明代理将其返回到调用客户端的堆栈上。

示例11-1:消息接收器的通用实现

public class GenericSink : IMessageSink

{

   IMessageSink m_NextSink;

  

   public GenericSink(IMessageSink nextSink)

   {

      m_NextSink = nextSink;

   }

  

   public IMessageSink NextSink

   {

      get

      {

         return m_NextSink;

      }

   }

   public IMessage SyncProcessMessage(IMessage msg)

   {

      PreCallProcessing(msg);

  

      //此处调用对象:

      IMessage returnedMessage = m_NextSink.SyncProcessMessage(msg);

     

      PostCallProcessing(returnedMessage);

  

      return returnedMessage;

   }

   void PreCallProcessing(IMessage msg)

   {

      /* 进行调用前处理 */

   }

   void PostCallProcessing(IMessage msg)

   {

      /* 进行调用后处理 */

   }

   public IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink)

   {

      /* 处理异步调用,然后: */

      return m_NextSink.AsyncProcessMessage(msg,replySink);

   }

}

消息接收器类型

     调用拦截可能会发生在两个地方。第一,接收器可以拦截进入上下文的调用,并进行一些调用前置处理和后置处理,如锁定和解除线程锁。这样的接收器称为服务器端接收器。第二,接收器可以拦截从上下文出去的调用,并进行一些调用前置处理和后置处理。这样的接收器称为客户端接收器。例如,使用客户端接收器,Synchronization属性可以选择跟踪同步域之外的调用,并解除锁定以允许其他线程访问。这是由客户端接收器进行的。你将看到如何安装接收器。

拦截进入上下文的所有调用的服务器端接收器称为服务器上下文接收器。拦截对特定对象的调用的服务器端接收器称为服务器对象接收器。服务器负责安装服务器端接收器。由客户端安装的客户端接收器称为客户端上下文接收器,它们会影响从上下文出去的所有调用。由对象安装的客户端接收器称为特使(envoy)接收器。特使接收器仅拦截与之关联的将特定对象的调用。客户端上的最后一个接收器和服务器端上的第一个接收器都是 CrossContextChannel类型的实例。接收器链是由各段组成的,每个段都是一个不同类型的接收器,如图11-5所示。因为在接收器链结束处必须有一个堆栈生成器才能转换消息,所以.NET在每个段的结束处都安装一个终结器。终结器是该段类型的一个接收器,它会对该段进行最终的处理,并将消息转发到下一段。例如,服务器上下文接收器段中的最后一个消息接收器称为ServerContextTerminatorSink。终结器的行为就像一个真正的死结:如果调用终结器上的IMessageSink.NextSink,则会获得一个空引用。实际的下一个接收器(下一个段中的第一个接收器)是终结器的一个私有成员。结果,你无法使用IMessageSink.NextSink从头到尾遍历整个拦截链。

提示:还有另外一种类型的接收器,称为动态接收器,它将允许在运行时按编程方式添加接收器,而不需要使用属性。动态接收器超出了本书的范围。

相同上下文调用(Same-Context Calls)

     跨上下文边界时必须通过代理来访问面向上下文的对象,以便各种接收器都可以就位拦截调用。现在的问题是,如果与对象在同一上下文的某个客户端将对象的引用传递到另一个上下文中的某个客户端时,会出现什么情况?如果同一上下文的客户端有该对象的直接引

图11-5:客户端和服务器段接收器链

用,那么.NET如何检测这一点,以及如何将代理引入到该对象和新的客户端之间?.NET是这样解决这个问题的,总是通过代理访问对象,即使是相同上下文中的客户端(见图11-6)。因为客户端和对象共享同一上下文,所以消息接收器无需执行任何调用前置处理和后置处理。拦截层由透明代理和真实代理以及一个消息接收器(堆栈生成器)组成。当同一上下文客户端将其对透明代理的引用传递到其他上下文中的客户端时,.NET将检测这一点,并在新客户端和该对象之间设置正确的拦截链。

图11-6:即使在同一上下文中,客户端也使用代理来访问面向上下文对象

提示:在COM+环境中,同一上下文的客户端拥有对象的直接引用。所以,开发人员必须手动地封送这些引用,也就是说,他们必须手动设置代理,并且必须协调这两个客户端间的封送协议。这经常是通过使用全局接口表(Global Interface Table,GIT)完成的。

面向上下文对象和Remoting(Context-Bound Objects and Remoting)

     面向上下文的对象是.NET Remoting的一种特殊情况(为一种客户端激活对象)。在许多方面,.NET都只是将它们作为远程对象进行处理,但.NET确实优化了Remoting架构的某些元素以利于面向上下文对象,例如,像之前提到的,用于跨上下文调用的通道是一个称为CrossContextChannel的经过优化的通道。对于真正的远程客户端激活对象,.NET将创建一个租约,并通过租约及其赞助者管理对象的生命周期。但是,因为面向上下文对象的客户端与其共享同一应用程序域,所以.NET仍然可以使用垃圾收集来管理对象的生命周期。事实上,当.NET创建面向上下文对象时,它仍然为其创建了一个租约,该对象甚至可以替换MarshaByRefObject.InitializeLifetimeService()并提供其自己的租约。但是,该租约不能控制对象的生命周期。

提示:对于真正的远程对象,当TCP或HTTP通道跨进程序边界封送一个引用时,这些通道将设置租约。对于跨上下文对象或跨应用程序域对象,CrossContextChannel和CrossAppDomain- Channel通道会忽略该对象的租约,而是让垃圾收集来管理远程对象。

posted @ 2008-09-25 11:23 bluealarm| 编辑
  2008年9月22日

     虽然Resource对于在XAML中定义对象的属性十分有用。但是Resource段的主要目的还是用来定义Styles。Styles是应用在元素上的属性值的基本集合。它可以具有批处理功能,能够弥补单一的Resource属性无法在XAML中应用到多个元素的不足。

     例如,Page中含有大量的button,我们希望这些button都使用一些相同的属性。我们可以将这些属性定义为一个Style,然后将这个style应用到所有的button。WPF中的style还可以处理属性的变化,这些变化可能来自其他属性的触发,也可能来自事件。

     Styles类属于System.Windows命名空间。它从Object继承,并且没有子继承。

     Style放在资源里,因为Style是“批处理”的资源,它可以修改多个对象的属性值,不从属于单独的元素对象。

     Styles有一个属性:x:Key属性,如果定义了此属性,就相当于是在资源中定义了一个名称为x:Key设定值的Style对象。如果没有定义此属性,那么这个Style将对属于这个Window中所有该控件类型生效。起到了批处理的效果。

     Style的最重要的属性是Setters。Setters是Setter和EventSetter等对象的集合。这些对象被称作“Setters”因为他们用来设置属性和事件处理的结果。

     Setter是Style的内容属性。如:

<Style ...>
    <Setter Property="Control.FontSize" Value="24" />
    <EventSetter ... />
    <Setter ... />
</Style>

 

 

posted @ 2008-09-22 17:36 bluealarm| 编辑
     摘要:   阅读全文
posted @ 2008-09-22 17:20 bluealarm| 编辑