2.1 Remoting Services
尽管DCOM解决了计算机间的分布问题,但其不能很好的透过防火墙/NAT。.NET提供了新的分布机制:Remoting API——使得可以使用TCP/HTTP等管道实现分布计算。
分布计算的一个实现方法:通过派生自System.MarshalByRefObject生成服务器类。MarshalByRefObject类或其派生类对象允许被不同的AppDomain、进程、计算机所访问。一个Marshal-by-reference对象要求在Client端建立一代理,而在Server端建立一个存根(stub),但这些工作都是自动生成的,我们要做的只是将服务组件声明为MarshalByRefObject的派生类。
例:通过管道机制派生自该类的服务组件声明:
using
System;
using
System.Runtime.Remoting;
using
System.Runtime.Remoting.Channels;
using
System.Runtime.Remoting.Channels.Tcp;
public
class CoHello:MarshalByRefObject
{
static void Main(string[] args)
{
//
所先管道类型对象的声明
TcpChannel channel = new TcpChannel(4000);
//
注册管道服务
ChannelServices.RegisterChannel(channel);
//
服务器配置信息
//1
、服务类型;、服务器名;、服务器发布的类型
RemotingConfiguration.RegisterWellKnownServiceType(typeof(CoHello), "HelloDotNet", WellKnownObjectMode.Singleton);
Console.WriteLine("Hit <Enter> to exit... ");
}
public void SayHello()
{ //
这是我们提供的远程服务!
Console.WriteLine("Hello, Universe of .NET.");
}
}
使用管道可以归结为如下几步:
1. 管道类型的选择:Channels命名空间下包含Tcp、Http、Ipc三个内层命名空间,他们分别对应三种管道机制;
2. 注册ChannelServices服务;
3. 注册服务器配置信息。
使用管道机制实现分布式应用程序的客户端需要注意以下几点:
1、不需要派生自MarshalByRefObject类;
2、当初始化一个TcpChannel对象时我们不需要使用端口号;
3、获得服务器对象;
4、使用服务器提供的服务。
例:上面CoHello类对应的客户端有:
using
System;
using
System.Runtime.Remoting;
using
System.Runtime.Remoting.Channels;
using
System.Runtime.Remoting.Channels.Tcp;
class Client
{
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
//
获得服务器类对象实例
CoHello h = (CoHello)Activator.GetObject(typeof(CoHello),
"tcp://127.0.0.1:4000/HelloDotNet"
);
h.SayHello();
Console.ReadLine();
}
}
2.2 COM+Services
Distributed Garbage Collector
.NET Remoting uses Lease to manage object LifeTime. If you’ve renewed the lease to an IP Address on DHCP network, you’ve pretty much figured out this mechanism because it’s based on similar concept.
.NET 分布对象的生命周期管理使用分发租期(Lease)替代COM的引用计数。远程对象所在的AppDomain包含一个Lease manager对象,该对象管理同所有远程对象相关联的Lease。当Lease过期,Lease Manager将会联系一个Sponsor,一个Sponsor是一个在调用Activation Call方法时,将它自己注册到Lease Manager的客户端对象。在lease过期后,如果Sponsor没有重新生成Lease,Lease Manager将移除相应的客户端对象。我们可以通过调用远程对象或lease类的Renew()类。
自定义属性
//
自定义属性时要注意设置该属性的用户
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttribute : Attribute
{
public string Autor;
public MyAttribute() { Autor = "
我自定义的属性"
; }
}
我通过
System.AttributeUsage类设置自定义属性的用户,这里我将属性的用户设置为类和结构。也就是说,可以在类和结构上使用该属性。下面代码就是在类上使用该属性的一个例子。
[MyAttribute]
public class Example
{ public void DoSomething() { }}
对于一个包含属性的类、方法或是其它成员,我们如何才能检验属性的使用呢?在
System.Attribute类和System.Reflection.MemberInfo类等中包含一个GetCustomAttributes()方法。
//
我们可以通过如下代码截获对象实例/类所包含的属性信息
object[] attrs = typeof(Example).GetCustomAttributes(false);
if ((attrs.Length > 0) &&(attrs[0] is MyAttribute))
{
MyAttribute myAttribute = (MyAttribute)attrs[0];
Console.WriteLine(myAttribute.Autor);
}
Transaction
在.NET中,我们只要使用几个属性就可利用COM+Services提供的事务。不同COM+Services在一开始就在COM+目录中注册COM+应用程序,.NET将在just-in-time时自动注册COM+应用程序的。
开发一个支持事务的.NET类,要求:一、该类继承自ServicedComponent,该类中实现了Com+ Serviced服务;二、使用恰当的属性。
另外可以使用System.EnterpriseServices.ContextUtil类获得COM+对象的上下文信息。该类提供了COM+主要基本方法的实现,如SetComplete()、SetAbort()、IsCallerInRole()等。但我们可以使用属性AutoCompleted替代该类的某些方法。例:
using System;
using System.Reflection;
using System.EnterpriseServices;
[assembly: ApplicationName("MyCom+Services")]
[assembly: ApplicationActivation(ActivationOption.Library)]
[assembly: AssemblyKeyFile("MyKey.pfx")]
[assembly: AssemblyVersion("2.0.0.0")]
[Transaction(TransactionOption.Required)]
public
class Customer : ServicedComponent
{ //
该方法中的ContextUtil可以通过在Add()上使用AutoCompleted属性来替换
public void Add(string strname)
{
try
{
Console.WriteLine("New Customer" + strname);
//
使用ContexUtil类,调用COM+的SetComplete()方法
ContextUtil.SetComplete();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
ContextUtil.SetAbort();
}
}
}
以上是
COM+组件的定义,但我们并不需要手工的注册该组件。我们需要做的仅仅是将该组件编译成dll文件。其注册行为发生在客户端执行Customer组件类的实例化时,如下面代码:
public
class Client
{
static void Main()
{
Customer c = new Customer();
c.Add("The first customer!");
}
}
问题:为什么我上面的代码只有在编译时才能将Customer这个COM+组件注册到我的计算机上呢?不是说在运行时自动注册的吗?可是我将该组件从MyCOM+目录中删除后,再次运行Client.exe并不能将组件注册。唯有编译,才注册了。这是出了什么问题了呢?
而我使用命令行,编译时虽然在每次执行Client.exe的确能够注册Customer组件到COM+组件目录中,但却又提示访问拒绝了!
2.3 对象池
一个类如果要支持对象池,首先该类必须继承自ServicesComponent;再就是覆盖所有的Activate()、DeActivate()、CanBePooled()方法;最后还得通过ObjectPooling属性设置对象池的大小。例
using
System;
using
System.Reflection;
using
System.EnterpriseServices;
namespace
ConsoleApplication1
{
[Transaction(TransactionOption.Required)]
[ObjectPooling(MinPoolSize = 1, MaxPoolSize = 5)]
public class Customer : ServicedComponent
{
public Customer()
{ Console.WriteLine("Some Expensive object construction!"); }
[AutoComplete]
public void Add(string strName)
{
//Add the new customer into the system,
//make the appropriate update to Server Database
Console.WriteLine("Add Customer {1} ",strName);
}
protected override void Activate()
{
//pooled object is being activated.
//perform the appropriate initialization.
Console.WriteLine("Activate!");
}
protected override void Deactivate()
{
//Object is about to be returned to the pool.
//perform the appropriate clean up.
Console.WriteLine("Deactivate!");
}
protected override bool CanBePooled()
{
//return the object to the pool.
Console.WriteLine("Can be Pooled!");
return true;
}
}
}
这里使用Activate和DeActivate方法来执行部分的初始化和资源清理工作。而调用CanBePooled()方法将告诉COM+,该对象(调用这个方法的对象实例)是否可以pooled(放入对象池,上面代码指示始终可以)。
对象池工作原理:在客户端初始化对象时,首先检查对象池中是否有空闲的对象,如果有的话,我们就从对象池中取出对象供我们使用;如果没有空闲的对象则将初始化一个新的对象,并将该新对象放入对象池(如果对象池大小不超届)。在使用一个从对象池中取出的对象,我们首先调用Activate;而在使用完该对象时,将调用DeActivate()方法;最后调用CanBePooled()方法判断该对象是否要放回对象池。
注意:COM+组件必须是公有的,而且其程序集还必须是强命名的。
COM+应用程序中的.NET组件都可以使用基于角色的安全机制。我们可以通过管理工具中的组件服务管理COM+组件并设置角色,角色是同Windows中的用户相关联的。另外,我们还可以使用代码实现角色的控制。
2.4 MessageQueue
如果电脑安装了MSMQ,我们可以引用System.Messaging.dll程序集开发消息队列应用程序。在System.Messaging命名空间中提供了消息队列应用程序需要的基本功能实现,诸如连接一个队列、打开一个队列、发送消息给队列、在队列中查找消息等等。例:
首先,定义消息对象,这里消息对象是一个结构类型对象。其结构定义如下:
public
struct Customer
{ public string First;
public string Last;
}
在使用消息队列时,首先判断该消息队列是否在计算机中存在,如果不存在,则必须新建消息队列。
string path = ".//PRIVATE$//NE_queue";
if (!MessageQueue.Exists(path))
{
//create out private queue!
MessageQueue.Create(path);
}
//Initialize the Queue!
MessageQueue q = new MessageQueue(path);
//create out object
Customer c = new Customer();
c.First = "Lin";
c.Last = "Fangzai";
//send out object to the queue.
q.Send(c);
这里我们通过
Send方法消息队列发送Customer类型的一个对象。另外,我们还可以从消息队列中获得消息:
MessageQueue
q = new MessageQueue(path);
string types ={ "Customer,DeQueue" };
((XmlMessageFormatter)q.Formatter).TargetTypeNames = types;
Message m = q.Receive(new TimeSpan(0,0,5));
Customer c = (Customer)m.Body;
首先我们打开由path路经指定的消息队列,而后将指定文件中存储的消息按要求提取出来并封装成MessageQueue类型的对象,最后使用Receive()方法获得消息值供客户端使用。
3 ADO and XML
所有的通信都有数据交换,而分布式组件间的通信则是通过Request/Response方法或基于消息的机制。现有的分布式组件都假设其组件间通信都使用相同的协议和数据格式。
微软的ADO.NET对象模型包含两组类型:Content Component和Managed-Provider Component。前者包含DataSet、DataTable、DataColumn、DataRow和DataRelation等类。而后者则多用于数据提取和数据更新。
在ADO.NET中,DataSet组件是以关系数据形式封装数据的,而在ADO以前版本,其实以数据表的形式封装的。
ADO中不同组件间的数据是以Recordset格式传送的。Recordset用表格的形式存储数据,不论其包含的信息是否来源自一个数据库,返回的数据同一个单一的数据表中返回的数据没有什么区别。ADO.NET允许多个Recordset在应用程序组件间共享,即并使用DataSet替代Recordset实现数据的传送。
DataSet可以被看出是一个数据库的内存视图,其可以包含多个DataTable和DataRelation对象。在以前ADO版本中,可以使用Recordset对象链来实现类似的功能(但不同的Recordset间的关系却不能如实的表现出来)。
由于ADO.NET是包含有分布式体系结构性质,ADO.NET实现了非连接数据集这一功能。由于非连接数据集必须跟踪数据集的变化,DataSet对象提供了方法以保证在对该数据集对象执行任何操作都可以将其反应到数据库中,也就是说,DataSet和数据库的一致性和准确性。这些方法有:HasChanges()、HasErrors、GetChanges()、AcceptChanges()、RejectChanges()等等。我们可以使用这些方法检验DataSet对象发生的变化,并以修改的数据对象形式获得修改信息;检查修改错误、接受/拒绝的修改等等信息。而如果要将这些修改传递回内存,只需要告知DataSet数据需要更新。
DataSet也在企业级Web应用程序被使用。企业级Web应用程序中,除非我们已经更新了数据或对数据库执行一些数据一致性任务,否则我们时不知道背后的真实数据的。
DataSet包含两个重要的群集:表集(DataTableCollection)和关系集(DataRelationCollection),我们可以通过DataSet的Property:Tables和Relations获取这两个群集对象。例:通过这两个属性创建表和关系。
//Add a new table to DataSet to m_ds' Collections tables
DataSet m_ds = new DataSet("MyDataSet");
m_ds.Tables.Add("Order");
m_ds.Tables["Order"].Columns.Add("OrderId",typeof (int));
m_ds.Tables["Order"].Columns.Add("CustomerName",typeof (string ));
m_ds.Tables["Order"].Columns.Add("Date",typeof(DateTime));
//register the column "order" as the primary key of the table order.
DataColumn[] keys = new DataColumn[1];
keys[0] = m_ds.Tables["Order"].Columns["OrderId"];
m_ds.Tables["Order"].PrimaryKey = keys;
//
创建第二个表
m_ds.Tables.Add("OrderDetail");
m_ds.Tables["OrderDetail"].Columns.Add("fk_OrderId",typeof(int));
//
关系的设置
DataColumn parentCol = m_ds.Tables["Order"].Columns["OrderId"];
DataColumn chirdCol = m_ds.Tables["OrderDetail"].Columns["fk_OrderId"];
m_ds.Relations.Add("Order_OrderDetail", parentCol, chirdCol);
m_ds.Relations["Order_OrderDetail"].Nested = true;
其中Nested指示这两个表间的关系是以嵌套结构表示的,这使得我们用XML处理这些实体变得更容易。
对于数据库的Insert操作,我们有:
DataRow
newRow = new DataRow();
newRow = m_ds.Tables[0].NewRow();
newRow["OrderId"] = 101213;
newRow["CustomerName"] = "Who are you?";
m_ds.Tables[0].Rows.Add(newRow);
3.1 XML and tables sets
DataSet提供了一些方法以使用XML技术,这些方法包括:WriteXml()、WriteXmlSchema()、ReadXml()、ReadXmlSchema()等。<>Schema方法只读如/写出框架内容(包括所有表其表间的关系),而WriteXml()和ReadXml()则还可以操作表数据。WriteXml()和WriteXmlSchema()均接受流、TextWriter、XmlWriter或字符表示的文件名。
例:将前面例子的数据表用XML方式输出/读取。
m_ds.WriteXml(Console.Out, XmlWriteMode.WriteSchema);
m_ds.WriteXml("DS_order.xml",XmlWriteMode.WriteSchema);
DataSet ds2 = new DataSet("RestoredDs");
ds2.ReadXml("DS_order.xml");
对于修改数据我们有:
m_ds.AcceptChanges();
//Begin to changes
m_ds.Tables["Order"].Rows[0]["OrderId"] = 102345;
if (m_ds.HasChanges())
{
//Gets a copy of the System.Data.DataSet that contains all changes made to
//it since it was loaded or since AcceptChanges() was lastcalled.
DataSet changeDS = m_ds.GetChanges();
//Writes the entire DataSet as a DiffGram, including original and
//current values.
changeDS.WriteXml("ChangeDs.Xml",XmlWriteMode.DiffGram);
//Commit all changes
m_ds.AcceptChanges();
}
3.2 DataTable
该类中包含两个群集对象:Rows和Columns。其中Columns记录表的结构,而Rows则记录每列的实际数据。数据表的字段为:DataColumn对象,而记录则用DataRow对象表示。该类还包含两个属性ParentRelations和ChildRelations用以记录数据表的关系。ChildRelations是个记录了以该表为主表的所有数据表的群集。
DataTable是同关系数据库中的数据表向挂钩的,也就是说,在数据库理论中存在的性质,该类也有相关的属性、域或方法等设置。如数据表的级连更新,就有:
m_ds.Relations["Order_OrderDetail"].ChildKeyConstraint.DeleteRule = Rule.Cascade;
这段代码的含义:删除时,级连问题的考虑。
DataView: 同数据库程序中的视图类似,同时在Windows Forms和Web Forms中提供了数据邦定功能。
DataRelation:可以使用该类定义DataTable对象间的关系。客户组件可以功过这些关系检查数据表是独立存在的还是具有层次结构的。一个DataRelation关系包括主表名、子表名、主表列(primary key)、子表列(foreign key)等几个部分。
3.3 Managed Providers
Managed Providers:一组实现了ADO.NET体系结构要求的固定函数集的.NET组件。
建立自己的Managed provider,我们必须具体化System.Data.Common命名空间中的DbDataAdapter类和并实现IDbCommand、IDbConnection和IDataReader接口。
微软提供了两个Managed Providers:OLE DB和SQL。
Connection
OleDbConnection和SqlConnection读实现了IDbConnection接口,因此,它们也通过继承得到所有connection对象所拥有的属性。同时它们也实现了IDbConnection接口的核心方法,如Open、Close等。
在ADO.NET中Connection对象不在支持事务功能,事务的实现封装进单独的Transaction对象中(OleDbTransAction和SqlTransaction),这是由于我们不在假设事务范围是整个connection,即可以在多个连级中交迭。创建一个新事物,可以调用相应的BeginTransaction()方法。
ADO.NET Connection中的函数也基本同ADO Connection中类似,但Execute()方法不在被实现,所有的执行读被封装在Command对象中。
3.4 The Command and Data Reader Objects
ADO.NET中的SqlCommand和OleDbCommand对象同ADO’s Command对象很相似。但ADO.NET中,Command对象是我们执行insert、update、delete等请求的唯一途径。
所有commands都通过SqlCommand或OleDbCommand的Connection属性同一个connection对象关联,connection对象是数据读取组件和数据库的连接管道。
To execute : the connenction must be open
Type of execution:
1、ExecuteReader() 返回IDataReader类型;
2、ExecuteNonQuery();
ADO.NET Command同ADO Command对象的返回值不同。ADO.NET中Recordset对象不在被支持,其所有的query Command执行的返回值是一个Data reader对象,该对象可以是OleDbDataReader或是SqlDataReader,以及任何实现了IDataReader接口的类。
使用Command、connection和data reader objects是低层的Managed provider工作方式。Data adapter封装了这些底层技术。
A data reader:同OOP中的流对象相似,单向(forward-only)、有序数据访问。是服务器端游标,也就是说在读取数据的整个过程到服务器的连接都必须是打开的。
例:OleDbConnection, OleDbCommand, OleDbDataReader组件的使用:
string sConn = "
数据库连接字符"
;
string sSql = "select au_fname,au_lname,phone from authors";
//
使用字符串新建连接
OleDbConnection oConn = new OleDbConnection(sConn);
oConn.Open();
OleDbCommand oCmd = new OleDbCommand(sSql, oConn);
OleDbDataReader oReader = oCmd.ExecuteReader();
//
取的指定列的顺序
//
由于DataReader是forward-only,所以用GetOrdinal()取得当前操作的位置
int idxFirstName = oReader.GetOrdinal("au_fname");
int idxLastName = oReader.GetOrdinal("au_lname");
int idxPhone = oReader.GetOrdinal("phone");
//Read():Advances the OleDbDataReader to the next record.
while (oReader.Read())
{
Console.WriteLine("{0} {1}'s phone: {2}", oReader.GetValue( idxFirstName ),
oReader.GetValue(idxLastName),
oReader.GetValue(idxPhone));
}
oConn.Close();
The DataAdapter object
DataAdapter对象:数据源和非连接DataSet间的桥梁,它包含一个同数据存储关联的连接以及将数据从数据存储中提取到DataSet中的DataTable的一些操作和将DataSet缓存中的数据更新到存储中的一些命令。尽管每一个DataAdapter映射到唯一一个DataSet中的DataTable组件,我们可以通过使用多个adapter组件装载到DataSet中的多个DataTable组件中。
OleDbDataAdapter和SqlDataAdapter都继承自DBDataAdapter组件,而DBDataAdapter友继承自抽象类DataAdapter,该类实现了接口IDataAdapter。这个接口提供了Fill、Update方法的声明。
OleDbDataAdapter.Fill():使用OleDbDataAdapter将Ole DB提供者中的数据提取到DataSet组件中。
OleDbDataAdapter.Update():将DataSet组件的更新提交。
DataAdapter组件使用步骤:
1、创建一个DataAdapter对象;
2、初始化SelectCommand对象的query字符传串;
3、初始化SelectCommand对象的Connection对象的连接;
4、初始化InsertCommand, UpdateCommand, DeleteCommand的query和connection字符串;
5、调用Fill()方法,并使用返回值装载dataset;
6、更新数据。
例:DataAdapter组件使用实例——复制DataSet对象
public static DataSet GenerateDS()
{
//Create one DataSet object.
DataSet ds = new DataSet("DBDataSet");
Project1.Properties.Settings a = new Project1.Properties.Settings();
OleDbDataAdapter dsAdapter1 = new OleDbDataAdapter
("select * from
表1"
,a.sConn);
OleDbDataAdapter dsAdapter2 = new OleDbDataAdapter
("select * from
表2"
, a.sConn);
dsAdapter1.Fill(ds, "
表1"
);
dsAdapter2.Fill(ds,"
标2"
);
//
由于DataAdapter是基于表的,所以还得重建不同表的关系。
ds.Relations.Add("
关系名"
,
主表列,子表列);
return ds;
}
3.5 DataSets and XML
XML解释器:Tree-based XML和Stream-based XML。
.NET Framework引入一种基于流的XML解释器:XmlReader。
Tree-based XML支持文档对象模型,我们必须了解:
1、XmlNode 及其派生;
2、XmlNodeList, XmlNode 的一个群集;
3、XmlNamedNodeMap, XmlAttribute的群集。
XmlNode是XML文档中单一节点的基类。在对象模型中,几乎所有的东西都继承自该类,如:XmlAttribute, XmlDocument, XmlElement, XmlTex以及其它的节点类型。作为一个基类,它提供了XML文档树创建的一些方法:AppendChild(),PrependChild(), InsertBefore(), InsertAfter(), Clone()等等。
XmlNode也为搜索XML文档提供了一组属性集:FirstChild, NextSibling, PreviousSibling, LastChild, ChildNodes, ParentNode等等。
XmlNodeList:支持enumeration, 是故我们可以通过群集获得所有的XmlNode对象。
XmlNamedNodeMap:是XmlAttribute对象群集。XmlAttribute enable both enumeration and indexing of attributes by name. 任何一个XmlNode都包含一个称为Attribute的property。
XmlDocument:继承自XmlNode,并扩展了XmlNode类。它包含一些辅助函数。这些辅助函数以用以创建其它类型的XmlNode的,如:创建XmlAttribute, XmlComment, XmlElement, 和XmlText等。另外该类还提供了保存和加载XML内容的机制。
下面是一个XML使用实例:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateElement("","Books",""));
XmlElement xmlRoot = xmlDoc.DocumentElement;
XmlElement xmlBook, xmlTitle;
XmlText xmlText;
xmlBook = xmlDoc.CreateElement("", "Book", "");
//
节点属性设置
xmlBook.SetAttribute("Category", "", "How to");
xmlTitle = xmlDoc.CreateElement("", "Title", "");
xmlText = xmlDoc.CreateTextNode("How to drive in DC metropolitan ?");
xmlTitle.AppendChild(xmlText);
xmlBook.AppendChild(xmlTitle);
//
添加孩子节点
xmlRoot.AppendChild(xmlBook);
//XmlDocument
支持Save、Load方法
xmlDoc.Save(@"C:/Documents and Settings/Administrator/
桌面/a.xml"
);
xmlDoc.Load(@"C:/Documents and Settings/Administrator/
桌面/a.xml"
);
xmlDoc.Save(@"C:/Documents and Settings/Administrator/
桌面/1a.xml"
);
XmlReader
XmlReader:无缓存、forward-only的快速访问streamed XML数据。它具有连个派生类XmlTextReader和XmlNodeReader。两者都是每次只读一个XML节点。两者的唯一区别是:XmlTextReader读取纯XML文本的流,而另一个者从XmlDocument中读取流节点。这流可以是整个XML文件或是该XML文件的一个部分。例:
StreamReader myStream = new StreamReader
(@"C:/Documents and Settings/Administrator/
桌面/a.xml"
);
XmlTextReader myTextReader = new XmlTextReader(myStream);
while (myTextReader.Read())
{
Console.WriteLine("Start Process:" + myTextReader.GetAttribute("Book"));
}
WriteReader:
XmlDataDocument:ADO.NET中DataSet同XML是兼容的。DataSet的框架可以被加载和保存为XML Schema Definition(XSD)。
XmlDataDocument可以同DataSet相关联。