<script type="text/javascript">
</script><script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script> name="google_ads_frame" marginwidth="0" marginheight="0" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-4239219102137748&dt=1189685071791&lmt=1189685071&format=468x60_as&output=html&correlator=1189685071791&channel=5813698985&url=http%3A%2F%2Fwww.cnham.com%2Funix%2Fhtml%2F14%2F2006%2F0201%2F6845.html&color_bg=FFFFFF&color_text=000000&color_link=000000&color_url=008000&color_border=FFFFFF&ad_type=text_image&ref=http%3A%2F%2Fwww.baidu.com%2Fs%3Ftn%3Dadvert%26ie%3Dgb2312%26bs%3D.NET%2BRemoting%2B%25CA%25B5%25CF%25D6%25B7%25D6%25B2%25BC%25CA%25BD%25CA%25FD%25BE%25DD%25BF%25E2%25B2%25E9%25D1%25AF%26sr%3D%26z%3D%26cl%3D3%26f%3D8%26wd%3D.NET%2BRemoting%2B%25CA%25B5%25CF%25D6%25B7%25D6%25B2%25BC%25CA%25BD%25CA%25FD%25BE%25DD%25BF%25E2%25B2%25E9%25D1%25AFcode%26ct%3D0&cc=705&ga_vid=1356692026.1189683797&ga_sid=1189683797&ga_hid=729737199&ga_fc=true&flash=9&u_h=1024&u_w=1280&u_ah=971&u_aw=1280&u_cd=32&u_tz=480&u_his=2&u_java=true" frameborder="0" width="468" scrolling="no" height="60" allowtransparency="allowtransparency">
简介 数据访问(Data access)、数据远程操作(data remoting)和数据操作(data manipulation)是 n- 层分布式数据库应用程序的三大主要组成部分。数据访问负责从关系数据库或任何持久性存储中检索数据。在本文中,我将使用与 Borland? C#Builder™ 一起发布的 Borland Data Provider (BDP) for Microsoft? Visual Studio .NET 来对 IBM? DB2? Universal Database™ (UDB) 进行数据访问。 数据远程操作负责对数据进行串行化或者使用代理对象(proxy object)和传输通道在不同层之间远程操作数据。数据串行化格式可以是二进制流、XML 或 SOAP,传输可以通过任何协议,比如 TCP/IP 或 HTTP。我将介绍 .NET remoting 的基础知识,并展示如何在层与层之间远程操作数据。 最后,数据操作是将原始数据转换成有意义信息的过程,这一过程要么通过客户机更新所呈现的数据来执行,要么通过在分布式应用程序的不同层上根据一些业务规则运行数据来执行。我将使用 ADO.NET Dataset 来进行客户机上的数据操作。
.NET remoting 远程操作(remoting)是创建任何分布式应用程序的主要成份。.NET remoting 为进程间通信提供了一个灵活的、可扩展的框架。有了 .NET remoting,您就可以与不同应用程序领域内的对象、在同一机器上运行的不同进程中的对象或者网络上不同机器中的对象进行交互。 .NET remoting 的当前实现提供了两种通道(TcpChannel 和 HttpChannel)、两种类型的格式器(binary 和 SOAP)、两种对象激活模式(Server-activated 和 Client-activated)以及一个生存期管理系统。该框架非常灵活,它可以通过使用配置文件来切换通道和格式器,而不需对代码作任何更改。而且,该框架还可以轻易地引入定制的通道和格式器。 一个 .NET 对象要成为可远程操作的对象,它必须要么是可串行化的,要么是从 MarshalByRefObject 继承过来的。因此,可远程操作的对象可以分成两类,要么是 按值编组(MBV),要么是 按引用编组(MBR)。
按值编组 MBV 对象要么实现 ISerializable 接口并因此提供它们自己的串行化,要么使用 SerializableAttribute ,让框架来负责它们的串行化。当需要远程操作一个 MBV 对象时,远程系统(remoting system)为该对象做一份完全拷贝并将其传递到调用它的应用程序的远程系统,在那里再重构这个对象。之后,对该远程对象的任何调用都直接针对这个本地拷贝。本地拷贝的状态更改不会传回给远程对象本身。 在以下情况下,MBV 对象是较好的选择:
- 客户机和服务器之间有大量的交互。由于所有的客户机调用都是直接针对远程对象的本地拷贝,因此不存在网络来回。
- 远程对象不是很大。
按引用编组 MBR 对象继承自 System.MarshalByRefObject 。当需要远程操作一个 MBR 对象时,根据激活模型要在客户机上创建一个代理对象。之后客户机把代理对象当作远程对象来调用。客户机上的 .NET remoting 系统编组对代理对象的所有调用,并发送请求到实际的远程对象。远程对象的状态及其行为保留在其创建时所在的应用程序领域内。 远程对象和客户机可能在同一应用程序领域内,这时不需要进行任何编组,因为客户机可以直接引用被请求的远程对象。
MBR 对象激活 远程操作基础设施既允许 server-activated 对象,也允许 client-activated 对象。server-activated 对象(SAO )只有客户机第一次在代理对象上调用方法时才创建。SAO 可以配置为 Singleton或 SingleCall这两种。 在 Singleton 模式下,只有一个实例以多线程的方式为所有客户机的请求服务。singleton 对象有一个相关的生存期,这样客户机就不会总是得到远程对象的同一个实例。远程对象的生存期可以使用生存期租用系统( System.Runtime.Remoting.Lifetime )服务进行更新。当您想要维护状态和在多个客户机之间共享数据时,请使用 Singleton 对象。 SingleCall 对象,顾名思义只为一个传入的客户机请求服务。对于每个请求,都要创建远程对象的一个新实例,之后再销毁。因此,这些对象的生存期是不可更新的,也不能维护状态。在负载平衡系统中,SingleCall 对象是最佳选择。 当收到来自某个客户机的请求时,便需要在服务器上创建 Client-activated 对象(CAO),以创建一个新的对象。在服务器创建了一个实例后,一个 ObjRef 对象被返回给客户机,并且在客户机上会创建一个代理对象。之后代理对象将客户机所有的方法调用编组并传递给远程对象。与 SAO 不同,CAO 可以使用一个非缺省的构造函数来创建,并且可以存储某一特定客户机的方法调用之间的状态信息。与 SAO 相同的是,CAO 可以使用相同的生存期租用系统来决定它们在内存中驻留多长的时间。
MBR 生存期 MBR 对象的生存期是由一个租用管理器和一个或多个 sponsor(发起人)控制的。Sponsor 这种对象可以通过向一个租用管理器注册来更新某一特定对象的租期。当一个 MBR 对象离开应用程序的领域时,该领域的租用管理器将负责创建该对象的生存期租期。租用管理器会周期性地检查租期是否到期。如果租期已到期,则联系该对象的 sponsor,询问它们是否需要续租。如果没有续租,则该租期将被删除,而该对象也将被垃圾收集。 您可以通过覆盖 MBR 对象中的 MarshalByRefObject.InitializeLifetimeService 方法来定制租期。该方法在 MBR 对象第一次被激活的时候调用。如果返回的是一个为空(null)的租期,则得到的是一个无限期的生存期。 使用租期进行生存期管理比起引用计数和不断地 ping 客户机(在 COM 中就是如此)要更好些,因为这样做减少了网络传输。此外,如果客户机出乎意料地离线了,也不会减少引用的数量而使得服务器一直运行下去。
.NET 数据访问和数据操作 到目前为止,我们已经看过了 .NET remoting 的基础知识。对于对 DB2 的数据访问和将数据交给客户机,我们将使用 ADO.NET 组件。ADO.NET 有两个核心组件:.NET Data Provider 和 Dataset。data provider 定义了一组接口,用于连接到数据源、执行 SQL 语句或存储过程以及检索单向 resultset。它还可以向 DataSet 提供数据和从中解析数据。DataSet 是关系数据在内存中的表示,它将 XML 和 XSD 更紧密地结合起来。 DataSet 由一个 DataTableCollection 和一个 DataRelationCollection 组成。每个 DataTable 都有一个 DataColumn 和 DataRow 的集合,用于表示表格数据。DataSet 和 DataTable 两种类型都是可远程操作的,因为它们从 MarshalByValueComponent 继承而来并实现了 ISerializable 接口。每个 DataRelation 表示 DataSet 中的两个 DataTable 之间的关系。在 DataSet 中也可以强加上数据完整性约束,例如惟一(unique)、主键(primary key)和外键(foreign key)。除了作为关系数据的缓存以外,DataSet 还提供排序、搜索和筛选的功能,并且可以跟踪对数据的更改。 BDP 是 Borland 中的一个 .NET data provider 实现。BdpConnection、BdpCommand 和 BdpDataReader 是数据访问的核心组件。其中 BdpDataAdapter 充当 DataSet 与数据源之间的管道,以提供和分解数据。随 BDP 一起的还有一套丰富的组件设计器,以方便使用。BDP 使您可以在设计时提供存活的数据、创建主-从(master-detail)关系以及将设计时数据与 ListControl 的任何后裔关联。 如果底层数据库对象是可访问的,并且应用程序使用了标准 ANSI SQL,那么通过使用配置文件您可以更改 BdpConnnection.ConnectionString,以便对不同的支持 BDP 的 RDBMS 运行应用程序,而不需更改任何代码。尽管在真正的应用程序中情况不会如此,但对于原型开发来说这是一个很好的特性。 对于第三方的集成,BDP 也提供了一套接口子集,您可以为自己的数据库实现这些接口,并在 C#Builder IDE 中加入 运行时、设计时和工具集成。 要了解更多关于使用 BDP 组件的细节,请参考 DB2 UDB 和 Borland 专区中的其他文章。
构建应用程序 概述 为了演示如何使用 BDP 远程操作 DB2 中的数据,我将使用一个简单的地址簿应用程序。这里的任务是查询某一特定地方的雇员并将他们转移到一个新的办公地点,然后用新雇员来填充这个新办事处的空缺位置,再将他们添加到地址簿中。为了简化问题,所有需要的数据都存储在单独一个 ADDRESSBOOK 表中。通过使用 DB2 命令行处理器,您可以执行以下 SQL 语句,以便在 DB2 中创建这个表并用一些新记录来填充它。
CREATE TABLE ADDRESSBOOK (EMPID
INTEGER NOT NULL PRIMARY KEY,
STREET VARCHAR(25), CITY
VARCHAR(25), STATE CHAR(2), ZIP
CHAR(5) )
INSERT INTO ADDRESSBOOK VALUES
(1,'100 Borland Way','Scotts
Valley','CA','95066')
INSERT INTO ADDRESSBOOK VALUES
(2,'100 Borland Way','Scotts
Valley','CA','95066')
| 表示可远程操作的类型 对于本文中的例子(参见 图 1),我有一个 Singleton SAO,它是一个 RemoteDataService,针对一个客户机请求返回一个 CAO RemoteDataProvider。所有客户机都连接到同一个 SAO,并获得各自的 CAO 实例。这些 CAO 为特定的客户机维护状态。 为了在客户机和服务器之间共享可远程操作的类型,我将这些类型表示为接口 IRemoteDataService 和 IRemoteDataProvider。现在您可以创建一个程序集(assembly)并在客户机和服务器上使用它。 也可以不使用接口来表示可远程操作的类型,而是使用抽象类。抽象类使您可以在多服务器场景下将远程对象从一个服务器传递到另一个服务器。使用接口或抽象类的一个不利的地方是不能使用配置文件。另一种不同的方法是使用 SoapSuds.exe 从一个服务器提取元数据,并生成一个新的程序集或源文件。之后客户机就可以使用只有元数据的程序集或源文件来了解可远程操作的类型。 IRemoteDataService 返回一个一般的 data provider 接口 IRemoteDataProvider,该接口包含属性 CommandText 和 Parameters,用以生成参数化的 SQL 请求。通常,所有这些事情都是在远程应用程序服务器上进行的,但是为了演示这种系统的灵活性,这里我让客户机来传递 SQL 命令和相关参数。GetData() 方法返回一个 DataSet,表示在服务器上执行参数化的 SQL 所得到的结果。SaveData() 方法将一个经过修改的 DataSet 传递给服务器。服务器则将 DataSet 的更改解析回数据库。 在碰到异常事件时,应用程序调用 GetError() 来接收来自服务器的 ErrorInfo 实例。.NET 异常将被按值编组,并自动提供给客户机。但是如果您想要定制异常,那么您可以编写一个定制的异常类,令其实现 ISerializable 接口,并提供一个以 SerializationInfo 和 StreamingContext 为参数的构造函数。为了演示 marshal-by-value 类型,我将使用一个 ErrorInfo 类,这个类只是简单地将服务器异常映射为一个有意义的错误消息,并返回一个 MBV 对象。ErrorInfo 类标有一个 Serializable 属性。 RemoteService.cs
namespace RemotingService
{
public interface IRemoteDataService
{
IRemoteDataProvider GetDataProvider();
}
public interface IRemoteDataProvider
{
string CommandText { get; set; }
BdpParameterCollection Parameters { get; }
DataSet GetData();
Int32 SaveData(DataSet ds);
ErrorInfo GetError();
}
[Serializable]
public class ErrorInfo
{
public Int32 Code;
public String Message;
public ErrorInfo() {}
}
} | 实现远程对象 远程接口 RemoteDataService 和 RemoteDataProvider 的实现是在服务器上进行的。RemoteDataService 继承 MarshalByRefObject,并实现 IRemoteDataService。GetDataProvider() 方法创建并返回 RemoteDataProvider 的一个新实例。 RemoteDataService 被注册为服务器上的一个 Singleton 对象,以便在第一个客户机调用 GetDataProvider() 时在服务器上创建 RemoteDataService 的一个新实例,并且只有一个 RemoteDataService 实例用于服务所有的客户机请求。 RemoteDataProvider 也是继承 MarshalByRefObject 并实现 IRemoteDataProvider。每个客户机在调用 GetDataProvider 时都得到 RemoteDataProvider 的一个新实例。之后客户机就可以设置 CommandText 和 Parameters 属性并调用 GetData()。GetData() 实现创建一个新的 BdpConnection,并指定一个 ConnectionString 以连接到 DB2。接着创建 BdpDataAdapter 的一个新实例,数据适配器上的 Fill() 方法则执行 CommandText 中指定的命令并填充 DataSet 中的一个 DataTable。然后创建一个 DataTable(即 Table1)并将其返回给客户机。 SaveData() 方法将客户机的更改传递到 Dataset,并通过调用 BdpDataAdapter 的 AutoUpdate() 方法将这些更改应用回 DB2。然后为了将客户机更改应用回数据库,AutoUpdate() 方法又使用 BdpCommandBuilder 来生成 update、delete 和 insert 等 SQL 语句。为了向 DB2 提供数据和从中解析数据,可以不使用 SQL,而是使用存储过程。在这种情况下,应该显式地指定 BdpDataAdapter SelectCommand、DeleteCommand、UpdateCommand 和 InsertCommand,并且 Update() 方法要负责解析更改。 警告:尽管通过使用 AutoUpdate() 可以使问题看上去简单一些,但是当前的实现并不总是生成最优的 SQL,也不处理主-从更新。因此,在这些问题解决之前,要清楚简单性是以性能代价换来的。 下面是服务器实现的一个代码片断。要获得服务器的完整清单,请参考源代码。 RemoteServer.cs
namespace RemotingServer
{
public class RemoteDataService :
MarshalByRefObject, IRemoteDataService
{
public IRemoteDataProvider GetDataProvider()
{
return new RemoteDataProvider();
}
public IRemoteDataProvider
GetDataProvider(String ClientID)
{
return new RemoteDataProvider(ClientID);
}
}
public class RemoteDataProvider :
MarshalByRefObject, IRemoteDataProvider
{
private String m_clientID = "";
private String m_commText = "";
private BdpParameterCollection m_Parameters
= new BdpParameterCollection();
private String m_connString =
@"Provider=DB2;Assembly=Borland.Data.Db2,Version=11.0.0,
Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;
Database=Sampledb;UserName=db2user;Password=mypass";
ErrorInfo m_Err = new ErrorInfo();
public RemoteDataProvider()
{
}
public RemoteDataProvider(String ClientID)
{
m_clientID = ClientID;
}
public string CommandText
{
get { return m_commText; }
set { m_commText = value; }
}
public BdpParameterCollection Parameters
{
get { return m_Parameters; }
}
public DataSet GetData()
{
DataSet ds = null;
try
{
BdpConnection Conn = new BdpConnection(m_connString);
BdpDataAdapter adapter = new BdpDataAdapter(m_commText, Conn);
if (Parameters != null && Parameters.Count > 0 )
adapter.SelectCommand.Parameters = Parameters;
ds = new DataSet();
adapter.Fill(ds,"Table1");
}
catch (Exception e)
{
//Map server exception to meaningful client error message
m_Err.Code = -1;
m_Err.Message = "Failure in GetData(): " e.Message;
throw e;
}
return ds;
}
public Int32 SaveData(DataSet ds)
{
try
{
BdpConnection Conn = new BdpConnection(m_connString);
BdpDataAdapter adapter = new BdpDataAdapter(m_commText, Conn);
if (Parameters != null && Parameters.Count > 0 )
adapter.SelectCommand.Parameters = Parameters;
if (ds.HasChanges())
{
adapter.AutoUpdate(ds,"Table1",BdpUpdateMode.All);
return 0;
}
else
return 1;
}
catch ( Exception e)
{
//Map server exception to meaningful client error message
m_Err.Code = -1;
m_Err.Message = "Failure in SaveData(): " e.Message;
throw e;
}
}
public ErrorInfo GetError()
{
return m_Err;
}
} | 主机 远程对象可以安放在一个受管的可执行文件中(控制台应用程序或 Windows Form),或 IIS 中,或者作为一个 Windows 服务。对于这里的例子,您将采取最简单的方法,那就是将远程对象安放在一个控制台应用程序中(参见 图 2)。 这里注册了一个新的 HTTP 通道,用以侦听端口 8000,用于 HTTP 的默认串行化格式是 SOAP。接着,将类 RemoteDataService 注册为一个 WellKnownServiceType,并带有一个 URI,这里激活模型设置为 Singleton。 RemoteServer.cs
public class RunServer
{
public static void Main(string[] s)
{
try
{
HttpChannel channel = new HttpChannel(8000);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType
(typeof(RemoteDataService),
"RemoteDataService.soap",
WellKnownObjectMode.Singleton);
}
catch (Exception e)
{
?
}
Console.WriteLine("Press enter to stop RemoteDataService...");
Console.ReadLine();
}
} | 客户机 同样,客户机也是一个控制台应用程序(参见 图 3),也创建一个 HTTP 通道并像前面一样注册。端口是可选的,因为可以自动分配一个端口。Activator.GetObject() 创建一个支持 IRemoteDataService 的本地代理对象。客户机然后调用 IRemoteDataService.GetDataProvider(),以获得 RemoteDataProvider 的一个实例。这时(只在第一次),在服务器上创建一个 server activation 对象。现在,为了从 DB2 获取数据,客户机设置 CommandText 和 Parameters 属性并调用 RemoteDataProvider 上的 GetData()。这时将更新返回的 DataSet,并且所有新的更新将通过调用 SaveData() 应用回 DB2。 下面是客户机的代码片断。要获得客户机的完整清单,请参考 源代码。 RemoteClient.cs
public class RemotingClient
{
public static void Main(string[] s)
{
IRemoteDataService remDS = null;
IRemoteDataProvider remDP = null;
HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
String ClientID = Guid.NewGuid().ToString();
try
{
remDS =
(IRemoteDataService)Activator.GetObject(typeof(IRemoteDataService),
"http://localhost:8000/RemoteDataService.soap");
if (remDS != null)
{
remDP = remDS.GetDataProvider(ClientID);
Int32 id = GetID(remDP);
DataSet ds = GetAddress(remDP);
UpdateAddress(ds);
AddAddress(ds, id);
DataSet ds1 = ds.GetChanges();
remDP.SaveData(ds1);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
//Get mapped error
if (remDP != null )
{
ErrorInfo Err = remDP.GetError();
Console.WriteLine("Mapped Error = " Err.Code ',' Err.Message);
}
}
}
} |
结束语 .NET Framework 提供了一些与分布式对象(例如 .NET Remoting、ASP.NET 和 Web Services)通信的方式。为了选择正确的进程间通信方式,您需要分析各种不同的因素,比如速度、可扩展性、互操作性、可伸缩性以及安全需要。 如果速度、灵活性和可扩展性很重要,那么 .NET Remoting 是最好的选择。如果要远程操作 DataSet 或其他可能很大的对象,那么 TCPChannel 或 HTTPChannel 结合 BinaryFormatter 这种方式比起 Web Services 来能提供更佳的性能。另一方面,如果您需要在异构的客户机和不同的操作系统间提供互操作性,那么 Web Services 是更好的选择。通常,Web Services 是无状态的,但是有了 .NET Remoting 之后,您既可以使用无状态的对象(SingleCall),也可以使用有状态的对象(Singleton),来构建更好的可伸缩的应用程序。 设计良好的分布式应用程序可以提供可伸缩性、负载平衡、容错并最终提供良好的性能。通过将整个的应用程序分离成逻辑上的几个层,例如表示层、业务规则层和持久性层,我们可以独立地管理、修改和增强各个层。
免责声明 本文包含样本代码。IBM 授予您(“被许可方”)使用这个样本代码的非专有的、版权免费的许可证。然而,该样本代码是以“按现状”的基础提供的,没有任何形式的(不论是明示的,还是默示的)保证,包括对适销性、适用于特定用途或非侵权性的默示保证。IBM 及其许可方不对被许可方由于使用该软件所导致的任何损失负责。任何情况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许可方都不对由使用该软件或不能使用该软件所引起的收入的减少、利润的损失或数据的丢失,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使 IBM 已经被明确告知此类损害的可能性,也是如此。 |