??xml version="1.0" encoding="utf-8" standalone="yes"?>
l护头文仉常是一个冗长乏呛_ƈ且容易出错的q程Q感?NET assemblycd抹去了对q些文g的倚赖?/P>
微Y.NET的可理的C++最l简化了我们创徏cd的方法,或更准确些,.NET assemblycdQ这U简化得以实C要是׃assemblycd是可以自我描q的。这对一个Managed C++E序员意味着什么呢Q基本上Ԍ一?assembly~译成功Q你在编译传lC++cd的时谨慎创徏的所有头文g除了l护的需要外Q都不再是必需的?/P>
׃言Q我感觉l护头文件是一U痛苦,所以我很高兴看到这U对头文件的倚赖正在消失。我l常会忘记需要在库中引用的一个或多个头文件的名称Qؓ了找到缺q数据cd定义Q我不得不对所有含有头文g的内容进行搜索,更糟p的是,源代码拆分Z半增加了我犯错误的机会。我l常犯的错误包括忘记所有的包含头文件打包、.h文g?cppq同步?/P>
既然对头文g的程序集不再是必需的,你可以用一U改q了的新Ҏ来创建源文g。但在考察q种Ҏ之前Q先让我们先来回一下创建程序集的传l方?/P>
传统Ҏ 在C++中,传统的库文g创徏Ҏ是徏立一pd头文件用以描q类库中所有的功能Q接下来Q你要在一个单独的源文件中实现q些头文件所定义了的所有功能。然后,在编译器中运行每一个源代码文gq同其相关的头文Ӟ以生成目标文Ӟ再将所有的目标文g怺链接来创建库文g 图A描述了这U传l方法?/P> 图A 创徏库的传统Ҏ 使用头文件的原因在于Q今后引用这个类的时候,所有的cR结构体、变量等{都可以很方便的定义?/P>
E序集可以用同LҎ生成Q这一q程的唯一区别在于Managed C++的标志是事先定的?/P>
在下面的例子中,列表A、B、C和D演示了如何用传统C++的方法创Z?assembly?/P>
列表Al出了头文gCards.h的定义,q个文g定义了一个包含扑克牌和Cardcȝ枚D(enmu)Q注意,使用ManagedC++Ӟ关键字public要放在enum和class之前的,因ؓq两者都需要在全局范围内可以被讉K?/P>
列表B展示了类的构造器和成员方法的实现Q列表C定义了第二个cDeckQ注意,虽然从来没有在头文g中定义,但CardcL在DeckcM内用的Q用这一技巧需要}讎ͼ在编译过E中Q头文g基本上是大批量地_脓到源文g中的。既然是q样的情况,我们只需单地在Deck.cpp源文件的包含文g列表中将Card.h攑֜Deck.h的前面,如此操作之后QCardcd首先被粘_l而被定义为DeckcL必需的类?/P>
列表D昄了在q个q你库中的源文gQ注意,正如我们刚刚所提到的,Card.h包含在Deck.h之前?/P>
在命令行中执行徏?assembly库的命oq不像火科学一栯难,其语法(没有q些省略P非常单: cl source1.cpp source2.cpp ... sourceN.cpp /CLR /LD /o OutputName.dll C++~译器取走这一pd源文件的名称Q然后是/CLR参数Q它告诉~译器我们正在用managed的扩展,l?LD参数告诉q接器去创徏一?dll文gQ最?o参数说明这?dll文g的名U?/P>
Z~译以上的例子,你可能会用到q行命oQ?/P>
clcard.cppdeck.cpp /CLR /LD /o cards.dll 新的 assemblyҎ 不再受困于分d文g和源文g的工作,׃ؓ新的库文件编码方式提供了可能性。在兜了一个圈子之后,我将开始介l我的简便方法,首先Q将所有的cMcL?.h)形式q行~码Q其中包括所有的定义和实玎ͼ然后使用一个单独的q接器文?.cpp)来包含所有的cLӞq种Ҏ唯一有些手的部分在于,你要定所有的cL件是按照正确的顺序排列的Q以保证一切需要调用的数据cd在用之前都已经定义好了Q即使用传l方法,q一问题同样需要处理。图B展示了这U新?assemblyҎ?/P> 图B 新的 assemblyҎ 采用q种ҎQ你只需l护半数文gQ既然定义和源码是一同定义的Q就不会造成不同步的。这样一来,l护库的工作q单了很多Q另外,开发文档只需保存在一个地方,也就更容易阅读,所有这些都说说明了q是一U更好的Ҏ?/P>
列表E、F和G展示了用新Ҏ生成的同L库?/P>
正如你看到的那样Q列表E是传l方法中Card.h和Card.cppq两个文件相l合而Ş成的Q我基本上只是把实现部分直接攑֜cȝ定义中,而不是在一个单独的文g中进行定义?/P>
同样的,列表F是传l方法中Deck.h和Deck.cppq两个文件相l合形成的,如果使用传统ҎQ你可以在这个类中访问其他的c而不必在文g中定义他们,q接器文仉要确保所有类的定义是按照正确的顺序排列的Q以保证q些cd使用之前都已l定义好了?/P>
在列表G中显C的q接器文件是唯一一个与众不同的文gQ它除了包含语句之外没有M其他的东西,ManagedC++~译器只~译.cpp文gQؓ了让~译器完成所有的工作Q你必须有这个文件。这U便利所带来的副作用是从库中d或删除类变得Ҏ了,而你已经组成库的类名称记录在了一个单一的地斏V在开发过E中Q连接器文gq是一个放|测试代码的理想场所?/P>
以这U方法徏立一个assemblycd的额外的优点是,在命令行条g下用时Q其命o很简单: clcards.cpp /CLR /LD l论 h自我描述功能?assembly器简化了我们~码的方法,q主要是因ؓ源代码模块不再需要被分离为头文g和源码文Ӟ正因为得益于此,我们展示了一U构?assembly库的新方法?/P>
?NET assemblycd
作者: silicon.com
2005-03-16 12:7 PM
责Q~辑Q?A href="mailto:li_ning@zdnet.com.cn?subject=http://www.zdnet.com.cn/developer/news/story/0,3800066930,39336568,00.htm">李宁
1.SQL Server数据库的q接
你可以用Connection对象的属性来指定数据源的位置及其它参数来q接数据库。如QSqConnection con=new SqlConnection("Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=database;use id=yourid,password=yourpassword;");
q是q接到本地数据库Q如果你惌到网l上的的数据库,p利用集成安全性,同时忽略用户名和密码。如Q?/P>
SqConnection con=new SqlConnection("Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=database;Integrated Security=SSPI");
如果使用的是ODBCq接到SQL ServerQ可以通过使用Trusted_ConnectionQyes;来用网l数据库?/P>
2.Oracle数据库的q接Q(前提Q必d安装Oracle客户端实用工L适当版本Qƈ创徏数据库别名,接着可以用以下的连接字W串q行q接Q?/FONT>
SqConnection con=new SqlConnection("Provider=MSDAORA;Data Source=dbAlias;User id=yourid,password=youpwd;);
3.Access数据库的q接Q(你可以用以下连接字W串来连接)
SqConnection con=new SqlConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\yourdb.mdb;User id=yourid,password=youpwd;);q接到数据库之后卛_调用Connection对象的Open()Ҏ来打开与数据库的连接,同理Close()Ҏ用来关闭与数据库的连接?/P>
q接池:
q接池是什么?在一个三层结构(或N层)中,当一个客L与中间层服务器进行通讯的时候,服务器会创徏一个与数据库连接,q执行操作的业务对象Q也是与数据库q接的实例)Q同时会创徏一?/FONT>Connection对象Q在攑֜一个池中(实际上是一个线E)。当释放q个实例的时候,此实例便关闭Q此时ƈ没有真正的关闭数据连接,而是Connection对象标记为关闭后存储在池中。如果这时再来启动一个新的业务对象,q时׃查现有的q接Q如果池中有打开的连接,即用它Q否则再创徏一个新q接?/FONT>
可能你会觉得很奇怪,如果q样Q那池中不是有很多的对象Q岂不是会浪费很多的资源Q这个问题的解决Ҏ是你可以讄与数据库的特定连接时_默认60U)Q如果在q个旉内未被用,.NET提供׃关闭此连接?/FONT>
如何打开q接池?默认情况下是打开?
如何关闭q接池?可以使用OleDbConnection.ReleaseConnectionPool()Ҏ来关闭连接池Q更或者你可以在OQI Qq接字符串中加上OLE DB Services= - 4;在用SqlConnection对象时可在连接符中加上Pooling=False。这时你再调用Close()时候便会真正地关闭与数据库的连接?/P>
Q注1Q可以用SQL事g探查器或性能监视器来观察q接到数据库中的q接数目Q以识别q接是否真正关闭或是只是攑օ池中。)
Q注2Q可以显式调用Dispose()Ҏ在垃圾收集器回收之前释放资源Q但如果你只是将Connection对象设ؓQUQL的话Q是不会断开与数据源的连接的Q?/P>
利用Connection对象来创建Command对象Q?ADO.NET中用Command对象来执行数据查询,更新Q例 Q?/P>
SqConnection con=new SqlConnection("Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=database;Integrated Security=SSPI");
using (OleDbCommand cmd=con.CreateCommadn())
{
cmd.CommandText="select * from table";
cmd.ExecuteNonQuery();
}
(注:此处使用using 的好处是在进行此ơ操作后便可释放资源?
利用Connection对象来创建Transaction对象Q?Transaction对象是ADO.NET中的事务理对象Q?/FONT>
例:
SqConnection con=new SqlConnection("Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=database;Integrated Security=SSPI");
con.Open();
OleDbTransaction tran=con.BeginTransaction(); (注:调用此方法会在连接时q回一个新的打开的Transaction对象来进行事务管理)
(注:事务是指一l单一实体q行的语句,可以保数据的完整性,防止因系l故障或其它原因而引L数据丢失。概念很抽象Q呵呵,看下d明白了)
事务有AQテQ四个属性(卛_子性,一致性,隔离性,持久性)Q?/FONT>
原子?/FONT>指在执行事务q程中,q个q程要么成功执行Q要么不执行?/FONT>
一致?/FONT>指事务前和事务后的数据的一致性,也就是如果事务成功执行的话,pȝp回成功的状态,x有数据的改变标记为已完成Q如没完成事务,卛_滚,q回到先前的合法状态?/FONT>
隔离?/FONT>指一个事务内的Q何变化都独立于其它的事务Q相对于两个事务的说法)
持久?/FONT>指事务是持箋的,也就是事务成功完成后的改变是怹的?/FONT>
Q注Q事务有手动和自动两U,本文的主题不在此Q事务的介绍在其它篇章会涉及刎ͼ
取得数据库的架构信息Q?/FONT>
有时你会发觉有需要获取数据库的架构信息来方便E序的运行。可使用OleDbConnection对象的GetOleDbSchemaTable()Ҏ来获取,它需要一个参数用来作q回的架构信息的qo器,卛_获取表中的列或行信息Q不写此参数则获取整个表所有列的信息?/P>
例:
OleDbConnectioncon=new OleDbConnection("Provider=SQLOLEDB;Data Source=MyServer;Initial Catalog=database;Integrated Security=SSPI");
con.Open();
DataTable dt=con.GetOleDbSchemaTable(OleDbSchamaGuid,null);
foreach(DataRow row in dt.Rows)
Console.WriteLine(row["column_name"].ToString());
---------------------------------------------------------------
关于AD.NET的连接对象还有很多有用的ҎQ具体查QSQN?/P>
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconusingannotationswithtypeddataset.asp
Annotations enable you to modify the names of the elements in your typed DataSet without modifying the underlying schema. Modifying the names of the elements in your underlying schema would cause the typed DataSet to refer to objects that do not exist in the data source, as well as losing a reference to the objects that do exist in the data source.
Using annotations, you can customize the names of objects in your typed DataSet with more meaningful names, making code more readable and your typed DataSet easier for clients to use, while leaving underlying schema intact. For example, the following schema element for the Customers table of the Northwind database would result in a DataRow object name of CustomersRow and a DataRowCollection named Customers.
<xs:element name="Customers"> <xs:complexType> <xs:sequence> <xs:element name="CustomerID" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element>
A DataRowCollection name of Customers is meaningful in client code, but a DataRow name of CustomersRow is misleading because it is a single object. Also, in common scenarios, the object would be referred to without the Row identifier and instead would be simply referred to as a Customer object. The solution is to annotate the schema and identify new names for the DataRow and DataRowCollection objects. Following is the annotated version of the previous schema.
<xs:element name="Customers" codegen:typedName="Customer" codegen:typedPlural="Customers"> <xs:complexType> <xs:sequence> <xs:element name="CustomerID" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element>
Specifying a typedName value of Customer will result in a DataRow object name of Customer. Specifying a typedPlural value of Customers preserves the DataRowCollection name of Customers.
The following table shows the annotations available for use.
Annotation | Description |
---|---|
typedName | Name of the object. |
typedPlural | Name of a collection of objects. |
typedParent | Name of the object when referred to in a parent relationship. |
typedChildren | Name of the method to return objects from a child relationship. |
nullValue | Value if the underlying value is DBNull. See the following table for nullValue annotations. The default is _throw. |
The following table shows the values that can be specified for the nullValue annotation.
nullValue | Description |
---|---|
Replacement Value | Specifies a value to be returned. The returned value must match the type of the element. For example, use nullValue="0" to return 0 for null integer fields. |
_throw | Throw an exception. This is the default. |
_null | Return a null reference or throw an exception if a primitive type is encountered. |
_empty | For strings, return String.Empty, otherwise return an object created from an empty constructor. If a primitive type is encountered, throw an exception. |
The following table shows default values for objects in a typed DataSet and the available annotations.
Object/Method/Event | Default | Annotation |
---|---|---|
DataTable | TableNameDataTable | typedPlural |
DataTable Methods | NewTableNameRow
AddTableNameRow DeleteTableNameRow |
typedName |
DataRowCollection | TableName | typedPlural |
DataRow | TableNameRow | typedName |
DataColumn | DataTable.ColumnNameColumn
DataRow.ColumnName |
typedName |
Property | PropertyName | typedName |
Child Accessor | GetChildTableNameRows | typedChildren |
Parent Accessor | TableNameRow | typedParent |
DataSet Events | TableNameRowChangeEvent
TableNameRowChangeEventHandler |
typedName |
To use typed DataSet annotations, you must include the following xmlns reference in your XML Schema definition language (XSD) schema.
xmlns:codegen="urn:schemas-microsoft-com:xml-msprop"
The following is a sample, annotated schema that exposes the Customers table of the Northwind database with a relation to the Orders table included.
<?xml version="1.0" encoding="utf-8"?> <xs:schema id="CustomerDataSet" xmlns:codegen="urn:schemas-microsoft-com:xml-msprop" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="CustomerDataSet" msdata:IsDataSet="true"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="Customers" codegen:typedName="Customer" codegen:typedPlural="Customers"> <xs:complexType> <xs:sequence> <xs:element name="CustomerID" codegen:typedName="CustomerID" type="xs:string" minOccurs="0" /> <xs:element name="CompanyName" codegen:typedName="CompanyName" type="xs:string" minOccurs="0" /> <xs:element name="Phone" codegen:typedName="Phone" codegen:nullValue="" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="Orders" codegen:typedName="Order" codegen:typedPlural="Orders"> <xs:complexType> <xs:sequence> <xs:element name="OrderID" codegen:typedName="OrderID" type="xs:int" minOccurs="0" /> <xs:element name="CustomerID" codegen:typedName="CustomerID" codegen:nullValue="" type="xs:string" minOccurs="0" /> <xs:element name="EmployeeID" codegen:typedName="EmployeeID" codegen:nullValue="0" type="xs:int" minOccurs="0" /> <xs:element name="OrderDate" codegen:typedName="OrderDate" codegen:nullValue="1980-01-01T00:00:00" type="xs:dateTime" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> <xs:unique name="Constraint1"> <xs:selector xpath=".//Customers" /> <xs:field xpath="CustomerID" /> </xs:unique> <xs:keyref name="CustOrders" refer="Constraint1" codegen:typedParent="Customer" codegen:typedChildren="GetOrders"> <xs:selector xpath=".//Orders" /> <xs:field xpath="CustomerID" /> </xs:keyref> </xs:element> </xs:schema>
The following code example uses a strongly typed DataSet created from the sample schema. It uses one DataAdapter to populate the Customers table and another DataAdapter to populate the Orders table. The strongly typed DataSet defines the DataRelations.
[Visual Basic] Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;" & _ "Initial Catalog=northwind") Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT CustomerID, CompanyName, Phone FROM Customers", & nwindConn) Dim orderDA As SqlDataAdapter = New SqlDataAdapter("SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM Orders", & nwindConn) ' Populate a strongly typed DataSet. nwindConn.Open() Dim custDS As CustomerDataSet = New CustomerDataSet() custDA.Fill(custDS, "Customers") orderDA.Fill(custDS, "Orders") nwindConn.Close() ' Add a strongly typed event. AddHandler custDS.Customers.CustomerChanged, & New CustomerDataSet.CustomerChangeEventHandler(AddressOf OnCustomerChanged) ' Add a strongly typed DataRow. Dim newCust As CustomerDataSet.Customer = custDS.Customers.NewCustomer() newCust.CustomerID = "NEW01" newCust.CompanyName = "My New Company" custDS.Customers.AddCustomer(newCust) ' Navigate the child relation. Dim customer As CustomerDataSet.Customer Dim order As CustomerDataSet.Order For Each customer In custDS.Customers Console.WriteLine(customer.CustomerID) For Each order In customer.GetOrders() Console.WriteLine(vbTab & order.OrderID) Next Next Private Shared Sub OnCustomerChanged(sender As Object, e As CustomerDataSet.CustomerChangeEvent) End Sub [C#] SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind"); SqlDataAdapter custDA = new SqlDataAdapter("SELECT CustomerID, CompanyName, Phone FROM Customers", nwindConn); SqlDataAdapter orderDA = new SqlDataAdapter("SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM Orders", nwindConn); // Populate a strongly typed DataSet. nwindConn.Open(); CustomerDataSet custDS = new CustomerDataSet(); custDA.Fill(custDS, "Customers"); orderDA.Fill(custDS, "Orders"); nwindConn.Close(); // Add a strongly typed event. custDS.Customers.CustomerChanged += new CustomerDataSet.CustomerChangeEventHandler(OnCustomerChanged); // Add a strongly typed DataRow. CustomerDataSet.Customer newCust = custDS.Customers.NewCustomer(); newCust.CustomerID = "NEW01"; newCust.CompanyName = "My New Company"; custDS.Customers.AddCustomer(newCust); // Navigate the child relation. foreach(CustomerDataSet.Customer customer in custDS.Customers) { Console.WriteLine(customer.CustomerID); foreach(CustomerDataSet.Order order in customer.GetOrders()) Console.WriteLine("\t" + order.OrderID); } protected static void OnCustomerChanged(object sender, CustomerDataSet.CustomerChangeEvent e) { }
Working with a Typed DataSet | Creating and Using DataSets | DataColumnCollection Class | DataSet Class
要操U|据库Q必d使用Connection来连接到数据库,再创Z个Command来查询。有几种创徏方式Q例Q?/P>
SqlCommand cmd;
string strCon="server=localhost;database=Northwind;Trusted_Connection=Yes;";
string strqry="select * from Categories";
SqlConnection con=new SqlConnection(strCon);
con.Open();
¹cmd=con.CreateCommand(); //q里使用用Connection对象的CreateCommandҎ来创Z个Command对象?BR>cmd.CommandText=strqry;
//SqlDataReader reader=cmd.ExecuteReader();
?̔cmd=new SqlCommand();?? //直接使用new 关键字来创徏
cmd.CommandText=strqry;
?cmd.Connection=con;?? //讄与数据库的连?/P>
cmd=new SqlCommand(strqry,con); //直接在new的时候带两个参数来创?/FONT>
执行方式Q?/FONT>
Q主要有q么几种Qcmd.ExecuteReader();cmd.ExecuteNonQuery();cmd.ExecuteScalar();cmd.ExecuteXmlReader();Q?/FONT>
Q,ExecuteReader();q回一个SqlDataReader对象或OleDbDataReader对象Q这个看你的E序的需要去 做。可以通过q个对象来检查查询结果,它提供了“游水”式的执行方式,即从l果中读取一行之后,Ud到另一行,则前一行就无法再用。有一点要注意的是执行之后Q要{到手动去调用Read()Ҏ之后QDataReader对象才会Ud到结果集的第一行,同时此方法也q回一个Bool|表明下一行是否可用,q回True则可用,q回False则到辄果集末尾?/FONT>
使用DataReader可以提高执行效率Q有两种方式可以提高代码的性能Q?FONT style="BACKGROUND-COLOR: #deb887">一U是Z序号的查找,一个是使用适当的GetҎ来查找?/FONT>因ؓ查询出来的结果一般都不会改变Q除非再ơ改动查询语句,因此可以通过定位列的位置来查找记录。用q种Ҏ有一个问题,是可能知道一列的名称而不知道其所在的位置Q这个问题的解决Ҏ是通过调用DataReader 对象的GetOrdinal()ҎQ此Ҏ接收一个列名ƈq回此列名所在的列号。例Q?/FONT>
int id=reader.GetOrdinal("CategoryName");
while(reader.Read())
{
Response.Write(reader[id]);
reader.Close();
?至于W二U方式很直观Q例Q?/FONT>
while(reader.Read())
{
?Response.Write(reader.GetInt32(0).ToString()+" "+reader.GetString(1).ToString()+"
");
}
DataReader的GetInt32()和GetString()通过接收一个列hq回一个列的|q两U是最常用的,其中 q有很多其它的类型?/FONT>
(注:DataReader对象在调用Close()Ҏ卛_闭与数据库的q接Q如果在没有关闭之前又重新打开W二个连接,则会产生一条异怿?
2.,ExecuteNonQuery()?q个Ҏq不q回一个DataReader对象Q而是q回一个intcd的|卛_执行之后在数据库中所影响的行数?/FONT>
例:
int affectrows=cmd.ExecuteNonQuery();
Response.Write(affectrows +" 条记录受影响");
?3,ExecuteScalar() q个Ҏ不接受Q何参敎ͼ仅仅q回查询l果集中的第一行第一列,而忽略了其它的行和列Q而且q回的是一个objectcdQ在使用之前必须先将它强制{换ؓ所需cd。如果返回的仅仅是一个单独的数据元,则可以用此Ҏ来提高代码的性能。例Q?/FONT>
string strCon="server=localhost;database=Northwind;Trusted_Connection=Yes;";
string strqry="select count(*) from Categories";
SqlConnection con=new SqlConnection(strCon);
con.Open();
SqlCommand cmd=con.CreateCommand();
int i=Convert.ToInt32(cmd.ExecuteScalar()); //必须强制转换
4,ExecuteXmlReader() 此方法用于QL操作Q返回一个XmlReader对象Q由于系l默认没有引?System.Xml名空_因此在用前必须前引入。例Q?/FONT>
string strCon="server=localhost;database=Northwind;Trusted_Connection=Yes;";
SqlConnection con=new SqlConnection(strCon);
con.Open();
SqlCommand cmd = new SqlCommand("select * from Categories FOR XML AUTO, XMLDATA", con);
XmlReader xr=cmd.ExecuteXmlReader();
Response.Write(xr.AttributeCount); //q里获取当前节点上的属性个?/FONT>?
xr.Close();
执行完毕之后Q照栯昑ּ地调用Close()ҎQ否则会抛出异常?/FONT>
使用参数化的查询
先看一DSQL语句Qselect CategoryID,Description from Categories where CategoryID=? 其中的问号就是一个参数。但在用的时候必L带有Q前~的命名参敎ͼ因ؓ.NET数据提供E序不支持这个通用的参数标记“??使用参数化的查询可以大大地简化编E,而且执行效率也比直接查询字符串要高,也更方便Q很多情况下都需要更Ҏ询字W串Q这U方式就提供了方便,只需更改参数的值即可。例Q?/FONT>
string strCon="server=localhost;database=Northwind;Trusted_Connection=Yes;";
SqlConnection con=new SqlConnection(strCon);
con.Open();
string strqry="select * from Categories where CategoryID=@CategoryID"; //带参数的查询
SqlCommand cmd=new SqlCommand(strqry,con);
cmd.Parameters.Add("@CategoryID",SqlDbType.Int,4); //l参数赋于同数据库中相同的类?BR>cmd.Parameters["@CategoryID"].Value="3"; //l参数赋|可灵zL?BR>SqlDataReader r=cmd.ExecuteReader();
while(r.Read())
{
Response.Write(r.GetString(2)+"
"); //取出指定参数列的?BR>}
con.Close(); //切记关闭
使用存储q程q行查询
先看D存储过E的形式Qcreate procedure cateproc (@CategoryID int(4)) as select * from Categories where CategoryID=@CategoryID? return?/FONT>
q个是数据库中的存储q程实现方式Q要在程序中调用存储q程Q一U方法是使用Command对象的 CommandType属性来实现。CommandType有三个枚丑ր|Text,TableDirect,StoredProcedure。只需CommandType属性设为第三个值即可实现调用存储过E。例Q?/FONT>
string strCon="server=localhost;database=Northwind;Trusted_Connection=Yes;";
SqlConnection con=new SqlConnection(strCon);
con.Open();
SqlCommand cmd=con.CreateCommand();
cmd.CommandText="cateproc";
cmd.CommandType=CommandType.StoredProcedure;
cmd.Parameters.Add("@CategoryID",SqlDbType.Int,4);
cmd.Parameters["CategoryID"].Value="2";
SqlDataReader r=cmd.ExecuteReader();
while(r.Read())
{
Response.Write(r.GetString(2)+"
");
}
con.Close();
其实在程序中实现调用存储q程的方式跟参数化查询很cMQ有Ҏ鞋翻新的味道?/FONT>
cmd.CommandType=CommandType.StoredProcedure;q种方式有个~点Q就是当要查询的表,视图或存储过E的名称中有Ҏ的字W(如空|的话Q则无法识别。因?/FONT>q有一U方式就?
cmd.CommandText="{Call cateproc(?)}"; //q里是调用存储过E,问号为参?BR>cmd.CommandType=CommandType.Text; //关键是这里?/FONT>
讄命o执行时
命o时是指Command对象在等待结果的旉Q(默认为3Q秒Q如果在Q0U内没执行查询,则Command抛出一个异常。也可以自己q行讄。例Qcmd.CommandTimeout=60;
取消执行查询
有时因某U原因,需要时取消命令的执行Q可调用Command对象的Cancel()Ҏ来退出执行,如果在未执行查询之前QCancel()不做Q何事?BR> DataAdapter查询的l果存储在DataSet或DataTable对象中,当执行这一q程的时候,DataAdapter使用了一个Command来与数据库通讯Qƈ在内部用了DataReader来获取查询结果,最后才结果复制到DataSet新行中去。这也是Fill的过E。如果有两个DataAdapter对象Q都使用相同的Connection对象Q在创徏的时候就会创Z个Connection对象Q而不是同一个,q种情况的解x案是Q?/P>
而不是将查询字符Ԍ单独写成一行?/P>
有时候可能ƈ不希望DataSet中的架构与数据库中的架构相同Q这U情늚解决Ҏ之一是可以采用别名的ҎQ即select id as Product ID,amount as Product Amount from product;另外一U解x案就是用DataAdapter提供的TableMappings集合机制Q通过它就可以查询结果映到DataSetl构中,q种Ҏ更方便,更灵zRTableMappings属性返回一个DataTableMappingsCollection对象Q其中包含了一lDataTableMappingsQ只要DataSet中相应的表名UC数据库中的表名相同,卛_以用它来创Z个映?DataSet中可以有多个?。TableMappings里还有一个ColumnMappings属性,其用法与TableMappings怼。其原理是DataAdapter从数据库d数据后,利用DataReader从结果集中获取列名称Q有一点特别要注意Q即只能获取列名U而无法获取表名称QDataAdapter事先假定表名UCؓTable,接着遇到映射语句则进行表映射。不说了Q看代码Q?/FONT> DataColumnMapping colMap; DataTableMapping tblMap=da.TableMappings.Add("Table","Ca"); //q里Table为关键,映射表名为Ca colMap=tblMap.ColumnMappings.Add("CategoryID","ID"); //映射列表 q行代码后就会发现DataGrid1上的列名为ID和描q?(^_^) Q注Q用DataTableMapping 之前要前导入名空间System.Data.Common;Q?/P>
q可以用AddRangeҎ来简化表和列的映:(一些代码同上面) ....... q种映射关系只能从数据库中读取展C给用户Q如果要映后的Table的更Ҏ交给数据库,q时库发现其中列与库中列不同Q便会发生异常,DataAdapter 同时也提供了MissingMappingAction属性来处理?/FONT> DataAdapter1.MissingMappingAction=MissingMappingAction.Passthrough/Ignore/Error 它接受MissingMappingAction的枚丑ր|Passthroughq个DC如果在库中找不CDataSet中相同列的话Q就此列映到库中QIgnore枚DDC忽略示出现的列QError表示找不到相应的列则抛出异常?/P>
分页Q?/P>
分页在应用中是常有的事,而DataAdapter本n也提供了分页的简单功能,如:DataAdapter1.Fill(dataset,startrow,rownum,“tablename?q种功能用于数据量较的查询可以,但当有大量数据的时候,׃发现q种分页的问题的存在。它的原理是假如有一百行数据Q分成十,每页十行Q当获取每一늚时候,q回前1Q行Q再接着Q第二页Q删除了前1Q行而获取接下的Q0行,在这一ơ中Q只是ؓ了获取1Q行数据Q但数据库却q回了2Q行Q依此类推,W1Q页的时候就会返回1Q0行,而DataAdapter本n帮我们删除了9Q行Q因为我们看上去q回的还是1Q行Q这U性能太低。因此本l介l另外一U性能较高的分|法。实际上q种分页Ҏ是将上一|后一行的键值存储下来,直接在SQL语句中就qo掉了Q不象前面那U到DataAdapterq边才过滤掉。例Q?/P>
con=new SqlConnection("server=localhost;database=Northwind;Trusted_Connection=Yes;"); q里假如上一|后一个键gؓ”BOTTM”,可以它用参数替代掉Q这样就查出了在'BOTTM'之后的5Q行。这U方法实现简单效率也高?/P>
首先QADO.NET中用了DataAdapter 来处理与数据库的联机与脱机。当时开发h员设计了DataAdapter是ؓ了能够处理脱机数据,方便操作Q关于这一点,只要调用其Fill()Ҏ卛_,q时会在DataSet中创Z个新的名为“Table“的DataTable.要重新指定名可用DataAdapter.Fill(DataSet,“Tabelname?。此时connection也关闭了。DataAdapter既可以用来提交查询,q将l果存储到DataSet中,也可以用来向数据库传递更攏V仅仅用其UpdateҎ卛_辑ֈ向数据库提交存储地DatSet中的更改?/P>
SqlConnection con=new SqlConnection("server=localhost;database=Northwind;Trusted_Connection=Yes;")
SqlDataAdapter da=new SqlDataAdapter("select CategoryID,Description from Categories",con);
SqlDataAdapter da=new SqlDataAdapter("select CategoryID,Description from Customers",con);
SqlConnection con=new SqlConnection("server=localhost;database=Northwind;Trusted_Connection=Yes;");
SqlDataAdapter da=new SqlDataAdapter("select CategoryID,Description from Categories",con);
DataSet ds=new DataSet();
colMap=tblMap.ColumnMappings.Add("Description","描述");
// Response.Write(tblMap.DataSetTable.ToString());
da.Fill(ds);
DataTable dt=ds.Tables["Ca"]; //q里是映后的表名,如果仍ؓ数据库的表名Q则无效Q特别注?BR> this.DataGrid1.DataSource=dt;
this.DataGrid1.DataBind();
DataTableMapping tblMap=da.TableMappings.Add("Table","Ca"); DataColumnMapping[] colMapArray=new DataColumnMapping[]{new DataColumnMapping("CategoryID","?nbsp; 品号"),new DataColumnMapping("Description","描述")}; tblMap.ColumnMappings.AddRange(colMapArray);
......
da=new SqlDataAdapter("select top 50 CustomerID,CompanyName from Customers where CustomerID>'BOTTM'",con);
ds=new DataSet();
da.Fill(ds,"Categories");
this.DataGrid1.DataSource=ds.Tables["Categories"]; this.DataGrid1.DataBind();
con.Close();
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Version 1.0.0
GotDotNet community for collaboration on this pattern
Complete List of patterns & practices
Context
You are implementing a distributed application with the .NET Framework. The client application displays a form that requires making multiple calls to an ASP.NET Web service to satisfy a single user request. Based on performance measurements you have found that making multiple calls degrades application performance. To increase performance, you would like to retrieve all the data that the user request requires in a single call to the Web service.
Background
Note: The following is the same sample application that is described in Implementing Data Transfer Object in .NET with a DataSet.
The following is a simplified Web application that communicates with an ASP.NET Web service to deliver recording and track information to the user. The Web service in turn calls a database to provide the data that the client requests. The following sequence diagram depicts the interaction among the application, the Web service, and the database for a typical page.
Figure 1 illustrates the sequence of calls needed to fulfill the entire user request. The first call retrieves the recording information, and the second call retrieves the track information for the specified recording. In addition, the Web service must make separate calls to the database to retrieve the required information.
Database Schema
The schema that is used in the example shown in Figure 2 depicts a recording record that has a one-to-many relationship with a track record.
Implementing a DTO
One way to improve the performance of this user request is to package all the required data into a data transfer object (DTO) that can be sent with a single call to the Web service. This reduces the overhead associated with two separate calls and allows you to use a single connection with the database to retrieve both the recording and the track information. For a detailed description of how this improves performance, see the Data Transfer Object pattern.
Implementation Strategy
A typed DataSet is a generated subclass of System.Data.DataSet. You provide an XML schema file which is then used to generate a strongly-typed wrapper around the DataSet. The following two code samples illustrate the differences. The first sample is implemented with an ordinary DataSet:
DataTable dataTable = dataSet.Tables["recording"]; DataRow row = dataTable.Rows[0]; string artist = (string)row["artist"];
This sample indicates that you need to know the table and column names to access the tables and fields contained in the DataSet. You also have to know the return type of the Artist field to ensure that the correct cast is done. If you do not use the correct type, you will get a runtime error. The following is the same example implemented with a typed DataSet:
Recording recording = typedDataSet.Recordings[0]; string artist = recording.Artist;
This example demonstrates the benefits that the typed interface provides. You no longer have to refer to table or column by name and you do not have to know that the return type of the Artist column is a string. A typed DataSet defines a much more explicit interface that is verifiable at compile time instead of at runtime. In addition to the strongly-typed interface a typed DataSet also can be used in all places a DataSet can be used; therefore, it also can be used as a DTO. It is loaded in a similar fashion as a DataSet and it can be serialized to and from XML. In comparison to an ordinary DataSet you do have to write and maintain an XML schema that describes the typed interface. The Microsoft Visual Studio .NET development system provides a number of tools that simplify the creation and maintenance of the schema.The rest of this implementation strategy outlines the steps required in creating a typed DataSet for the sample application just described.
Creating a Typed DataSet
A typed DataSet is generated from an XML schema. Visual Studio .NET provides a drag-and-drop tool which automates the creation of the schema (see Figure 3) and the generation of the typed DataSet classes. If you do not use Visual Studio.NET, you can write the XML schema and use a command-line tool called XSD.exe to generate the typed DataSet. For detailed instructions on both of these methods, see "Typed DataSets in ADO.NET" from the May 2001 issue of .NET Developer [Wildermuth02].
RecordingDto.xsd
The following is the XML schema for the DTO to be used in this example. It combines both the recording table along with its associated track records in a single typed DataSet named RecordingDto:
<?xml version="1.0" encoding="utf-8" ?> <xs:schema id="RecordingDto" targetNamespace="http://msdn.microsoft.com/practices/RecordingDto.xsd" elementFormDefault="qualified" attributeFormDefault="qualified" xmlns="http://tempuri.org/RecordingDTO.xsd" xmlns:mstns="http://msdn.microsoft.com/practices/RecordingDto.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:codegen="urn:schemas-microsoft-com:xml-msprop"> <xs:element name="RecordingDto" msdata:IsDataSet="true"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="recording" codegen:typedName="Recording" codegen:typedPlural="Recordings" codegen:typedChildren="Track"> <xs:complexType> <xs:sequence> <xs:element name="id" type="xs:long" codegen:typedName="Id" /> <xs:element name="title" type="xs:string" codegen:typedName="Title" /> <xs:element name="artist" type="xs:string" codegen:typedName="Artist" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="track" codegen:typedName="Track" codegen:typedPlural="Tracks" codegen:typedParent="Recording"> <xs:complexType> <xs:sequence> <xs:element name="id" type="xs:long" codegen:typedName="Id" /> <xs:element name="title" type="xs:string" codegen:typedName="Title" /> <xs:element name="duration" type="xs:string" codegen:typedName="Duration" /> <xs:element name="recordingId" type="xs:long" codegen:typedName="RecordingId" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> <xs:unique name="RecordingDTOKey1" msdata:PrimaryKey="true"> <xs:selector xpath=".//mstns:recording" /> <xs:field xpath="mstns:id" /> </xs:unique> <xs:unique name="RecordingDTOKey2" msdata:PrimaryKey="true"> <xs:selector xpath=".//mstns:track" /> <xs:field xpath="mstns:id" /> </xs:unique> <xs:keyref name="recordingtrack" refer="mstns:RecordingDTOKey1"> <xs:selector xpath=".//mstns:track" /> <xs:field xpath="mstns:recordingId" /> </xs:keyref> </xs:element> </xs:schema>
This schema is not the exact file produced by Visual Studio .NET. It is annotated with a number of attributes that are prefixed from the codegen namespace. This modification is desirable because the code that is generated does not adhere to the .NET naming conventions. For example, without the modification, Visual Studio .NET would generate a track class that corresponds to the track table, whereas according to conventions used in the .NET Framework the class should be named Track. To change the name of the class that is generated, you must add the codegen:typedName attribute to the element definition in the XML schema:
<xs:element name="track" codegen:typedName="Track"> </element>
There are a number of other attributes besides codegen:typedName. For a detailed description of all the attributes, see "Typed DataSets in ADO.NET" from the May 2001 issue of .NET Developer [Wildermuth02].
Filling a Typed DataSet from the Database
The following code example demonstrates how to fill a typed DataSet with the data that the sample application requires. This includes the specific recording record and all of its associated track records. The difference between this code and filling an ordinary DataSet is that you do not need to explicitly define the relationship between the recording and track records.
Assembler.cs
Just as in Implementing a Data Transfer Object in .NET with a DataSet, an Assembler class maps the actual database calls into the typed DataSet:
using System; using System.Data; using System.Data.SqlClient; using Recording; public class Assembler { public static RecordingDto CreateRecordingDto(long id) { string selectCmd = String.Format( "select * from recording where id = {0}", id); SqlConnection myConnection = new SqlConnection( "server=(local);database=recordings;Trusted_Connection=yes;"); SqlDataAdapter myCommand = new SqlDataAdapter(selectCmd, myConnection); RecordingDto dto = new RecordingDto(); myCommand.Fill(dto, "recording"); String trackSelect = String.Format( "select * from Track where recordingId = {0} order by Id", id); SqlDataAdapter trackCommand = new SqlDataAdapter(trackSelect, myConnection); trackCommand.Fill(dto, "track"); return dto; } }
Note: The example shown here is not meant to describe the only way to fill the typed DataSet. There are many ways to retrieve this data from the database. For example, you could use a stored procedure.
Using a Typed DataSet in an ASP.NET Page
As mentioned previously, a typed DataSet inherits from System.Data.DataSet. This means that it can be substituted for a DataSet. For example, when using the .NET user interface controls (Web Forms or Windows Forms) a typed DataSet can be used in all places you could use a DataSet. The sample application page shown in the following code example uses two DataGrid controls, RecordingGrid and TrackGrid. You can use the typed DataSet, RecordingDto when setting the DataSource properties on the controls because a typed DataSet inherits from DataSet.
using System; using System.Data; using RecordingApplication.localhost; public class RetrieveForm : System.Web.UI.Page { private RecordingCatalog catalog = new RecordingCatalog(); // protected void Button1_Click(object sender, System.EventArgs e) { string stringId = TextBox1.Text; long id = Convert.ToInt64(stringId); RecordingDTO dto = catalog.Get(id); RecordingGrid.DataSource = dto.recording; RecordingGrid.DataBind(); TrackGrid.DataSource = dto.track; TrackGrid.DataBind(); } }
Tests
Because the typed DataSet is generated by tools in the .NET Framework, you do not need to write tests to verify that it functions correctly. In the following tests, you are testing that the Assembler class loaded the typed DataSet correctly.
AssemblerFixture.cs
using NUnit.Framework; using System.Data; using Recording; [TestFixture] public class AssemblerFixture { private RecordingDto dto; private RecordingDto.Recording recording; private RecordingDto.Track[] tracks; [SetUp] public void Init() { dto = Assembler.CreateRecordingDto(1234); recording = dto.Recordings[0]; tracks = recording.GetTracks(); } [Test] public void RecordingCount() { Assert.Equals(1, dto.Recordings.Rows.Count); } [Test] public void RecordingTitle() { Assert.Equals("Up", recording.Title.Trim()); } [Test] public void RecordingChild() { Assert.Equals(10, tracks.Length); foreach(RecordingDto.Track track in tracks) { Assert.Equals(recording.Id, track.RecordingId); } } [Test] public void TrackParent() { RecordingDto.Track track = tracks[0]; RecordingDto.Recording parent = track.Recording; Assert.Equals("Up", parent.Title.Trim()); } [Test] public void TrackContent() { RecordingDto.Track track = tracks[0]; Assert.Equals("Darkness", track.Title.Trim()); } [Test] public void InvalidRecording() { RecordingDto dto = Assembler.CreateRecordingDto(-1); Assert.Equals(0, dto.Recordings.Rows.Count); Assert.Equals(0, dto.Tracks.Rows.Count); } }
These tests describe how to access the individual elements of the DataSet. Because of the use of a typed DataSet, the test code does not require the actual column names and does not require the return type to be cast. Comparing these tests with the ones described in Implementing Data Transfer Object in .NET with a DataSet reveals the differences between using a strongly-typed interface and a generic interface. The strongly-typed interface is easier to use and understand. It also provides the added benefit of compile-time checking on return types.
Resulting Context
Implementing DTO with a typed DataSet shares a number of the same benefits and liabilities as implementing DTO with a DataSet; however, certain benefits and liabilities are unique to a typed-DataSet implementation.
Benefits
The typed DataSet shares the following benefits with a DataSet when used as a DTO:
Development tool support. Because the DataSet class is implemented in ADO.NET, there is no need to design and implement the DTO. There is also extensive support in Visual Studio for automating the creation and filling of DataSet and typed-DataSet objects.
Integration with controls. A DataSet works directly with the built-in controls in Windows Forms and Web Forms, making it a logical choice as a DTO.
Serialization. The DataSet comes complete with the ability to serialize itself into XML. Not only is the content serialized, but the schema for the content is also present in the serialization.
Disconnected database model. The DataSet represents a snapshot of the current contents of the database. This means that you can alter the contents of the DataSet and subsequently use the DataSet as the means to update the database.
An additional benefit that might persuade you to use a typed DataSet as opposed to an ordinary DataSet is the strongly-typed interface of the typed DataSet. A typed DataSet, as described here, generates classes that can be used to access the contained data. The classes present an interface which defines how the class is to be used in a more explicit manner. This removes the need for casting which was present in the DataSet implementation.
Liabilities
The typed DataSet shares the following liabilities with a DataSet when used in the context of a DTO:
Interoperability. Because the DataSet class is part of ADO.NET, it is not the best choice for a DTO in cases requiring interoperability with clients that are not running the .NET Framework.. You can still use DataSet, however, the client will be forced to parse the XML and build its own representation. If interoperability is a requirement, see Implementing Data Transfer Object in .NET with Serialized Objects.
Stale data. The typed DataSet, like a DataSet, is disconnected from the database. It is filled with a snapshot of the data in the database when it is constructed. This implies that the actual data in the database may be different from what is contained in the typed DataSet. For reading primarily static data, this is not a major issue. If the data is constantly changing, however, using any kind of DataSet is not recommended.
Potential for performance degradation. Instantiating and filling a DataSet can be an expensive operation. Serializing and deserializing a DataSet can also be very time consuming. A good rule of thumb for using a DataSet is that a DataSet is a good choice when you are using more than one table or relying on the capability of the DataSet to update the database. If you are displaying the results from a single table, then using a DataReader with strongly-typed objects may offer better performance. For more information, see Implementing Data Transfer Object in .NET with Serialized Objects.
The following are additional liabilities when using a typed DataSet as opposed to an ordinary DataSet:
A typed DataSet is still a DataSet. A typed DataSet can be substituted at runtime with a DataSet. This means that even though the strongly-typed interface exists, programmers can still access the data without the typed interface. A possible result of doing this is that there could be parts of the code which couple the application tightly to the DataSet table and column names.
The need for an XML schema. When using a typed DataSet you have to create and maintain an XML schema to describe the strongly-typed interface. Visual Studio .NET provides a number of tools to assist in this process, but nevertheless you still have to maintain an additional file.
Related Patterns
For more information, see the following related patterns:
Implementing Data Transfer Object in .NET with Serialized Objects.
Assembler. In Enterprise Application Architecture Patterns, Fowler defines Assembler as a specialized instance of the Mapper pattern [Fowler03].
Acknowledgments
[Beau02] Beauchemin, Bob. Essential ADO.NET. Addison-Wesley, 2002.
[Fowler03] Fowler, Martin. Enterprise Application Architecture Patterns. Addison-Wesley, 2003.
[Wildermuth01] Wildermuth, Shawn. "Typed DataSets in ADO.NET." .NET Developer. May 2001.
今天x一个Class览工具Q就像VS.NET中的对象览器。后来在http://www.dotnettoolbox.com/toolbox/ViewCategory.aspx?ID=5Q这个网站也是个不错的地方)发现q个工具QReflector for .NET ?BR>
|址:
http://www.aisto.com/roeder/dotnet/
下蝲地址:
http://www.aisto.com/roeder/dotnet/
注意Q下载时要输一些注册信息,输入用户名时Q中间要加一个空根{?BR>功能介绍Q?BR>Reflector is a class browser for .NET components (assemblies). It supports assembly and namespace views, type and member search, C# XML documentation viewer, reference search, IL disassembler, VB and C# decompiler, dependency trees, supertype/subtype hierarchies and resource viewers. Function prototypes are displayed in C# and VB syntax.