[日期:2009-02-13] | 來源:志偉教程網(wǎng) 作者:志偉教程網(wǎng) |
C#分部方法的語法
在看C#語言的What'sNew時,突然發(fā)現(xiàn)新特性列表的最后,多出了一個“Partial MethodDefinitions”,但并不像其他新特性一樣有超鏈接鏈接到其說明。上網(wǎng)搜索了一下,關(guān)于分部類型的信息非常少。尤其是中文信息,英文技術(shù)文章中,倒是有兩篇不錯的:http://blogs.msdn.com/wesdyer/archive/2007/05/23/in-case-you-haven-t-heard.aspx和http://community.bartdesmet.net/blogs/bart/archive/2007/07/28/c-3-0-partial -methods-what-why-and-how.aspx。
又仔細(xì)看了一下MSDN Library for Visual Studio 2008 Beta 2,終于對這個語言特性有所了解,在這里介紹一下,希望對大家有所幫助。
分部方法的定義和分部類型類似,只需在方法定義前添加partial關(guān)鍵字。但分部方法只能拆分成兩個部分——一部分是定義聲明(Definition Declaration),另一部分是實現(xiàn)聲明(ImplementDeclaration)。其中定義聲明看上去和抽象方法類似:
partial class CA
{
// ……
private void partial M(); // 定義聲明|
而實現(xiàn)聲明看上去和普通方法類似:
private void partial M() // 實現(xiàn)聲明
{
// 方法體
}
在調(diào)用分部方法時,和調(diào)用其他方法一樣:
CA a = new CA();
a.M();
只是,如果只有定義聲明而沒有編寫實現(xiàn)聲明,則編譯器不會發(fā)射(Emit)該方法和調(diào)用該方法的語句的元數(shù)據(jù)與IL代碼。換言之,如果沒有編寫實現(xiàn)聲明,則編譯得到的程序集中,CA類型里并沒有M這個方法。本文發(fā)表于www.bianceng.cn(編程入門網(wǎng))
使用分部方法的注意事項
分部方法的語法非常簡單,但有一些事項要注意。
如果沒有寫實現(xiàn)聲明,則不會發(fā)射方法調(diào)用代碼,也不會對參數(shù)進(jìn)行求值。因此,對于下面的例子:
class CA
{
partial void M(int i);
static void Main()
{
CA a = new CA();
int i = 0;
a.M(i++);
}
}
分部方法M只有定義聲明,沒有實現(xiàn)聲明,因此也不會發(fā)射調(diào)用該方法的代碼:a.M(i++),因此也不會對i++進(jìn)行求值。所以最終i的值依然是0.但如果為M編寫了實現(xiàn)聲明,則a.M(i++)的代碼會被編譯到最終的程序集中,同時參數(shù)也被求值,i的值將被變?yōu)?。
分部方法只能出現(xiàn)在分部類中。
分部方法必須是私有(private)的,并且返回值類型必須是void。
分部方法可以帶有參數(shù),并且其參數(shù)可以帶有this、params和ref修飾符,但不能帶有out修飾符。
分部方法不可以是虛擬(virtual)的。
分部方法不可以是外部(extern)的。
分部方法可以是靜態(tài)(static)的,也可以是不安全(unsafe)的。
分部方法可以是泛型方法,泛型約束必須放置在定義聲明中,但也可以在事先聲明中重復(fù)說明。在定義聲明和實現(xiàn)聲明中,類型參數(shù)和類型參數(shù)的名字不一定必須一致。本文發(fā)表于www.bianceng.cn(編程入門網(wǎng))
不能將分部方法封裝到一個委托中。
分部方法的應(yīng)用場景
分部方法和分部類型的初衷是類似的,一方面可以使得不同的開發(fā)者能夠同時編寫一個類型的不同部分,另一方面可以分離自動生成的代碼和用戶手寫的代碼。和分部類型一樣,分部方法也會在編譯初期被合并成一個方法定義。猜測:從微軟的角度來看,第二個“初衷”可能才是真正的初衷。
由此,分部方法有如下幾個應(yīng)用場景:(場景1 出自In Case You Haven'tHeard這篇文章「http://blogs.msdn.com/wesdyer/archive/2007/05/23/in-case-you-haven-t-heard.aspx),場景2出自Visual Studio 2008的Linq to SQL技術(shù),而場景3則是AndersLiu自已臆想出來的。
場景1:輕量級事件處理
有的時候,自動生成的代碼需要事件這類語言構(gòu)造來通知用戶對某些操作進(jìn)行處理,但實際上用于編寫的代碼就位于自動生成的類型之中。此時,或者需要觸發(fā)一個事件,或者就需要生成一個virtual方法來讓用戶繼承。但無論是事件還是繼承,開銷都是比較大的,所以可以通過分部方法來實現(xiàn)輕量級的處理方式。如下面的類:(本例子引用自前述的In Case You Haven't Heard一文)。
partial class Customer
{
string name;
public string Name
{
get
{
return name;
}
set
{
OnBeforeUpdateName();
OnUpdateName();
name = value;
OnAfterUpdateName();
}
}
partial void OnBeforeUpdateName();
partial void OnAfterUpdateName();
partial void OnUpdateName();
}
這里定義了三個分部方法,其意義不言而喻。假設(shè)這是系統(tǒng)自動生成的代碼,則我們只需在另外一個源代碼文件中的partial class Customer中實現(xiàn)這幾個分部方法即可。
場景2:自定義DataContext中的Insert、Update、Delete方法
當(dāng)使用Linq to SQL向項目中加入了實體類之后,還會創(chuàng)建一個XxxDataContext類,這個類繼承自DataContext類,并且是partial的。這個類封裝了具體的數(shù)據(jù)庫操作功能(實體類僅封裝數(shù)據(jù)庫中的數(shù)據(jù)),如對象的插入、更新和刪除等。
下面我們來看一下這個自動生成的類定義:
[System.Data.Linq.Mapping.DatabaseAttribute(Name="AdventureWorks")]
public partial class AdventureWorksDataContext : System.Data.Linq.DataContext
{
private static System.Data.Linq.Mapping.MappingSource mappingSource
= new AttributeMappingSource();
#region Extensibility Method Definitions
partial void OnCreated();
partial void InsertAWBuildVersion(AWBuildVersion instance);
partial void UpdateAWBuildVersion(AWBuildVersion instance);
partial void DeleteAWBuildVersion(AWBuildVersion instance);
......
這里我們可以看到一系列的partial方法。其中第一個OnCreated實際上屬于場景1中描述的情況,是一個輕量級的事件,表示DataContext環(huán)境對象創(chuàng)建完畢。而其他partial方法則用于自定義DataContext的IUD操作。對于每一個表(實體類),這里都會出現(xiàn)一組InsertXxx、UpdateXxx和DeleteXxx方法。如果我們希望自定義刪除行為(如希望將一個IsDelete字段設(shè)置為true來表示已刪除),則可以在另一個文件中擴(kuò)展這個partial類,并為對應(yīng)的Delete方法提供實現(xiàn)聲明。
場景3:新的調(diào)試信息輸出方法
這是Anders Liu臆想的場景,在分部方法的協(xié)助下,我們可以寫出這樣的代碼:
partial class CA
{
partial void DebugPrint(string msg);
...
void F()
{ ....
DebugPrint("aaa");
}
}
partial class CA
{
#if DEBUG partial void DebugPrint(string msg);
{
Debug.WriteLine(msg);
}
#endif
}
這樣做的好處在于,我們還是反過來說罷,如果不這樣做,必須在每次調(diào)用調(diào)試代碼時都加入#if判斷。而這樣可以將調(diào)試代碼都寫成方法,在一處用#if進(jìn)行判斷。
缺點在于,由于分部方法必須是私有的,所以必須針對每個類寫一套調(diào)試代碼。
小結(jié)
嗯,總而言之,Anders Liu在這篇文章里說的是分部方法。
在Web編程中,我們常需要把一些本地文件上傳到Web服務(wù)器上,上傳后,用戶可以通過瀏覽器方便地瀏覽這些文件,應(yīng)用十分廣泛。
那么使用C#如何實現(xiàn)文件上傳的功能呢?下面筆者簡要介紹一下。
首先,在你的Visual C# web project 中增加一個上傳用的WebForm,為了要上傳文件,需要在ToolBox中選擇HTML類的File Field控件,將此控件加入到WebForm中,然而此時該控件還不是服務(wù)端控件,我們需要為它加上如下一段代碼:
<form method=post encType=multipart/ form-data runat="server">
,這樣它就成為服務(wù)端控件了,如果需要同時上傳數(shù)個文件時,我們可以相應(yīng)增加此控件。
需要注意的是代碼中一定要把 的屬性設(shè)置成為:
<form method=post encType=multipart/ form-data runat="server">
如果沒有這個屬性,就不能實現(xiàn)上傳。
然后在此Web Form中增加一個Web Form類的Button,雙擊Button添加如下代碼:
//上傳圖片的程序段
DateTime now = DateTime.Now ;
//取現(xiàn)在時間到DataTime類的對象now中
string strBaseLocation = "D:\web\FC\pic\";
//這是文件將上傳到的服務(wù)器的絕對目錄
if (uploadfile1.PostedFile.ContentLength != 0)
//判斷選取對話框選取的文件長度是否為0
{
uploadfile1.PostedFile.SaveAs(strBaseLocation+now.
DayOfYear.ToString()+uploadfile1.PostedFile.
ContentLength.ToString()+".jpg");
//執(zhí)行上傳,并自動根據(jù)日期和文件大小不同為文件命名,確保不重復(fù)
Label1.Text="圖片1已經(jīng)上傳,文件名為:"+now.DayOfYear.
ToString()+uploadfile1.PostedFile.ContentLength.ToString()+".jpg";
navigator.Insert(System.Xml.TreePosition.After,
XmlNodeType.Element,"pic1","","") ;
navigator.Insert(System.Xml.TreePosition.FirstChild,
XmlNodeType.Text,"pic1","","") ;
navigator.Value= now.DayOfYear.ToString()+uploadfile1.
PostedFile.ContentLength.ToString()+".jpg" ;
navigator.MoveToParent() ;
}
上面的代碼用于筆者開發(fā)的一個使用XML文件存儲新聞信息的系統(tǒng)中,后面幾句代碼作用是寫上傳文件信息到XML文件中。如果要上傳其他類型文件,只需要將jpg改為相應(yīng)類型的后綴名即可,如改為doc即可上傳Word文件,瀏覽器即可直接瀏覽上傳的Word文件。
【注意事項】
1. 上傳文件不可以無限大;
2. 要注意IIS的安全性方面的配合;
3. 用Visual Studio 的安裝項目做安裝程序的時候,請注意安裝程序所在的絕對路徑問題;
4. 注意文件上傳后的重名問題。
簡介
僅僅使用一行簡單的程序,你就能夠使你的Windows窗體的所有菜單和上下文菜單具有office2003的菜單外觀。同樣地,你也可以只用一行程序,就能為你的菜單加上漂亮的圖標(biāo)。本文實現(xiàn)的是一個具有該功能的組件。如果你想讓你的菜單恢復(fù)原來的外觀,也只須調(diào)用End方法即可。
組件的使用
要正確使用組件,必須先將你的組件加入到工具箱中。然后將該組件從工具箱中拖放放到form窗體中。這時會看到你的form的設(shè)計頁中多出了一個名為OfficeMenus1的圖標(biāo),說明已經(jīng)將菜單組件加入到了form中。緊接著調(diào)用如下方法:
//開始顯示office 2003菜單
OfficeMenus1.Start( FormName ); 注:FormName為要改變菜單風(fēng)格的窗口名稱。同樣,你也可以通過調(diào)用如下方法終止菜單的office2003風(fēng)格,使之回到原始外觀: // 改變菜單的外觀風(fēng)格到原始狀態(tài)
OfficeMenus1.End();為菜單頂添加圖標(biāo)也很簡單,只須為工程添加一個ImageList(圖像列表控件),然后將OfficeMenu組件的ImageList屬性更改為你添加的ImageList,使用如下代碼實現(xiàn): // 為菜單添加圖像
// OfficeMenus.AddPicture( MenuItem MenuItemToAddPictureTo, int ImageIndex );
OfficeMenus1.ImageList = imageList1;
OfficeMenus1.AddPicture(menuItem2, 1);
可以看出,只須如此幾行代碼就能輕松讓你的菜單實現(xiàn)office2003風(fēng)格。
組件的實現(xiàn)方法及原理
組件由三個類實現(xiàn),這三個類分別為OfficeMenus,MainMenuItemDrawing和MenuItemDrawing,都位于命名空間Dev4Arabs中。由于實現(xiàn)代碼較長,所以在此只給出了組件實現(xiàn)的思路。
組件實現(xiàn)的第一步是從System.ComponentModel.Component類派生類OfficeMenus。定義如下:public class OfficeMenus : System.ComponentModel.Component然后在類中定義兩靜態(tài)變量:
//圖像列表用來存儲菜單中用到的圖標(biāo) static ImageList _imageList;//存儲圖片細(xì)節(jié)的一個名稱集合,NameValueCollection的詳細(xì)說明請查閱MSDN,該類主要用來使每個菜單的句柄與每個圖標(biāo)形成一一對應(yīng)的關(guān)系,以便后面繪制菜單頂?shù)膱D標(biāo)時快速地找到某個菜單所對應(yīng)的圖標(biāo)。 static NameValueCollection picDetails =new NameValueCollection();接下來定義公
ODP.NET 11g是Oracle發(fā)布的供.NET程序訪問Oracle數(shù)據(jù)庫的ADO.NET組件,比微軟自帶的Oracle組件性能好,更可以訪問UDT(User Defined Type)類型,Procedure,REF等等高級Oracle特性。
.NET 1.1的客戶端需要的發(fā)布文件如下:
◆Oracle.DataAccess.dll (odt111odp.netin1.x)
◆OraOps11.dll (odt111in)
.NET 2.0需要發(fā)布:
◆Oracle.DataAccess.dll (odt111odp.netin2.0)
◆OraOps11w.dll (odt111in)
上面的客戶端均需要OCI基本包支持:
◆oci.dll
◆oraociei11.dll (也可以用更小的oraociicus11.dll代替)
◆orannzsbb11.dll
為了在客戶端測試方便,還可以加上SQL*Plus包,包括兩個文件:
◆sqlplus.exe
◆orasqlplusic11.dll
發(fā)布sqlplus包可以使用sqlplus "user_name/password@//192.168.1.31:1521/ORCL"在客戶端測試Oracle的狀態(tài)。
根據(jù)上面原則,最小的ODP.NET Oracle客戶端發(fā)布文件包括5個文件,壓縮后大小為8MB:
◆oci.dll
◆oraociicus11.dll
◆orannzsbb11.dll
◆Oracle.DataAccess.dll
◆OraOps11.dll
概述
任何有實際價值的關(guān)系數(shù)據(jù)庫應(yīng)用程序都離不開一大堆的查詢表。如果您是開發(fā)圖形用戶界面的專家,那么您知道這些查詢表將用于加工下拉列表框中的列表。我將查詢表分成兩種:只讀表和可改寫只讀表。二者的區(qū)別在于什么會導(dǎo)致表的改變。我認(rèn)為如果需要召開員工會議或者用戶會議才可以修改表的內(nèi)容,那么表就是只讀的。一個好的例子就是公司的產(chǎn)品類別表。表的內(nèi)容將不會改變直到公司研發(fā)并向市場投放了新產(chǎn)品,或者公司進(jìn)行了重組??筛膶懙闹蛔x表是內(nèi)容相對固定的列表,但可以被最終用戶修改,通常使用組合框而不用下拉列表框來展現(xiàn)??筛膶懼蛔x表的一個例子就是稱謂術(shù)語表。應(yīng)用程序設(shè)計人員能夠考慮到最常用的那些稱謂,如Ms., Mr.,Mrs.以及Dr.,但總有某個用戶的頭銜是您從未考慮過的而且希望把它添加進(jìn)來。舉個例子看看這種情況有多么常見,我最后工作的一個中型產(chǎn)品有一個設(shè)計良好符合第三范式的關(guān)系數(shù)據(jù)庫,它包含了350—400張表,我估計了一下大約有250張表是只讀的或可改寫只讀表。
傳統(tǒng)的Web應(yīng)用程序(三層結(jié)構(gòu)應(yīng)用程序的經(jīng)典范例)希望盡可能地緩存這種類型的表。這樣不僅可以減少往返數(shù)據(jù)庫的次數(shù),還可以降低數(shù)據(jù)庫服務(wù)器的查詢負(fù)載、提高某些應(yīng)用場景的響應(yīng)能力,例如接受了新訂單。只讀表很容易緩存;可以始終將表放在緩存中,偶爾需要重新加載表時,則由數(shù)據(jù)庫管理員(DBA)通過某種方式重新加載緩存。我希望您的企業(yè)中很少召開對數(shù)據(jù)庫基本結(jié)構(gòu)和內(nèi)容作修改的會議。重新刷新中間層緩存中可改寫只讀表則有一點點麻煩。如果緩存刷新的頻率太低就無法獲得您期望的效果;用戶也無法立刻看到其它用戶對數(shù)據(jù)的修改。支持人員可以使用另一個應(yīng)用程序添加新項,然后給打算使用該項的同伴發(fā)送一條即時消息,但同伴的選擇列表框中卻并不包含新添加的項。更糟糕的是,如果第二個用戶此時試圖重新添加這條“缺失的列表項”,就會收到一條數(shù)據(jù)庫錯誤告知該項已經(jīng)存在了。由于類似問題的存在,如果可改寫只讀表允許多點更新,那么通常不進(jìn)行緩存。
過去,程序員不得不自己手工開發(fā)解決方案,使用消息隊列、進(jìn)行文件輸出的觸發(fā)器、或者out-of-band協(xié)議來通知緩存,如果應(yīng)用程序外部的某些用戶更新了可改寫只讀表。這種“通知”解決方案只是通知緩存有行被添加或修改了,指示必須刷新緩存。如何通知緩存有哪些行改變了或者有哪些行被添加了,這一直都是個難題,是分布式數(shù)據(jù)庫和分布式事務(wù)或者合并復(fù)制領(lǐng)域的問題。低成本的通知解決方案的做法是:只要程序收到“緩存無效“的消息就刷新整個緩存。
SqlDependency 提供了緩存解決方案
如果您正在使用SQL Server 2005和ADO.NET2.0,那么一種新的稱為查詢通知的通知解決方案已內(nèi)置在SqlClient數(shù)據(jù)供應(yīng)商和數(shù)據(jù)庫中。該問題終于有了內(nèi)置且易于使用的解決方案!ASP.NET2.0也內(nèi)置了用于支持查詢通知的特性。ASP.NET的Cache對象可以注冊通知,甚至還可以將通知與ASP.NET的頁面緩存或頁面片段緩存結(jié)合在一起使用。
實現(xiàn)該功能的架構(gòu)中包含了SQL Server 2005查詢引擎、SQL Server ServiceBroker、sp_DispatcherProc系統(tǒng)存儲過程,ADO.NET的SqlNotification(System.Data.Sql.SqlNotificationRequest)和SqlDependency類(System.Data.SqlClient.SqlDependency),以及ASP.NET的Cache類(System.Web.Caching.Cache)簡而言之,它的工作方式如下:
1.每個ADO.NET SqlCommand都包含一個Notification屬性,表示對通知的請求。
當(dāng)執(zhí)行SqlCommand時,如果存在Notification屬性,那么就會在網(wǎng)絡(luò)請求中附加一個表示通知請求的網(wǎng)絡(luò)協(xié)議包(TDS)。
2.SQL Server使用查詢通知架構(gòu)注冊所請求通知的訂閱,然后執(zhí)行命令。
3.SQL Server“監(jiān)視”任何可能導(dǎo)致最初返回的結(jié)果集發(fā)生變化的SQL DML語句。當(dāng)發(fā)生變生時,就向Service Broker服務(wù)發(fā)送消息。
4.消息可以:
a.引發(fā)通知并將通知傳回注冊的客戶端。
b.駐留在Service Broker's的服務(wù)隊列中,高級客戶端可以實現(xiàn)自己的處理機(jī)制。
圖1. 通知服務(wù)概覽
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sqltoolforexcuteandadapter]
(
@objName nvarchar(100),--存儲過程名稱
@isexcute int --是否為execute 或者是sqladapter 0是execute,1是sqladapter
)
AS
SET NOCOUNT ON
DECLARE @parameterCount int
DECLARE @errMsg varchar(100)
DECLARE @parameterAt varchar(1)
DECLARE @connName varchar(100)
DECLARE @outputValues varchar(100)
--Change the following variable to the name of your connection instance
SET @connName='conn.Connection'
SET @parameterAt=''
SET @outputValues=''
SELECT
dbo.sysobjects.name AS ObjName,
dbo.sysobjects.xtype AS ObjType,
dbo.syscolumns.name AS ColName,
dbo.syscolumns.colorder AS ColOrder,
dbo.syscolumns.length AS ColLen,
dbo.syscolumns.colstat AS ColKey,
dbo.syscolumns.isoutparam AS ColIsOut,
dbo.systypes.xtype
INTO #t_obj
FROM
dbo.syscolumns INNER JOIN
dbo.sysobjects ON dbo.syscolumns.id = dbo.sysobjects.id INNER JOIN
dbo.systypes ON dbo.syscolumns.xtype = dbo.systypes.xtype
WHERE
(dbo.sysobjects.name = @objName)
AND
(dbo.systypes.status <> 1) --不理解這個不等于1是干嘛的?在sql幫助中也沒有???
ORDER BY
dbo.sysobjects.name,
dbo.syscolumns.colorder
SET @parameterCount=(SELECT count(*) FROM #t_obj)
IF(@parameterCount<1) SET @errMsg='No Parameters/Fields found for ' + @objName
IF(@errMsg is null)
BEGIN
print 'SqlConnection conn = new SqlConnection("");
SqlCommand com = new SqlCommand("'+@objName+'", conn);'
print 'com.CommandType = CommandType.StoredProcedure;'
PRINT ' SqlParameter[] Parameters = new SqlParameter[' +
cast(@parameterCount as varchar) + '];'
PRINT ''
DECLARE @source_name nvarchar,
@source_type varchar,
@col_name nvarchar(100),
@col_order int,
@col_type varchar(20),
@col_len int,
@col_key int,
@col_xtype int,
@col_redef varchar(20),
@col_isout tinyint
DECLARE cur CURSOR FOR
SELECT * FROM #t_obj
OPEN cur
-- Perform the first fetch.
FETCH NEXT FROM cur INTO
@source_name,@source_type,@col_name,@col_order,@col_len,@col_key,
@col_isout,@col_xtype
if(@source_type=N'U') SET @parameterAt='@'
-- Check @@FETCH_STATUS to see if there are any more rows to fetch.
WHILE @@FETCH_STATUS = 0
BEGIN
SET @col_redef=(SELECT CASE @col_xtype
WHEN 34 THEN 'Image'
WHEN 35 THEN 'Text'
WHEN 36 THEN 'UniqueIdentifier'
WHEN 48 THEN 'TinyInt'
WHEN 52 THEN 'SmallInt'
WHEN 56 THEN 'Int'
WHEN 58 THEN 'SmallDateTime'
WHEN 59 THEN 'Real'
WHEN 60 THEN 'Money'
WHEN 61 THEN 'DateTime'
WHEN 62 THEN 'Float'
WHEN 99 THEN 'NText'
WHEN 104 THEN 'Bit'
WHEN 106 THEN 'Decimal'
WHEN 122 THEN 'SmallMoney'
WHEN 127 THEN 'BigInt'
WHEN 165 THEN 'VarBinary'
WHEN 167 THEN 'VarChar'
WHEN 173 THEN 'Binary'
WHEN 175 THEN 'Char'
WHEN 231 THEN 'NVarChar'
WHEN 239 THEN 'NChar'
ELSE '!MISSING'
END AS C)
--Write out the parameter
PRINT ' Parameters[' + cast(@col_order-1 as varchar)
+ '] = new SqlParameter("' + @parameterAt +
@col_name+ '", SqlDbType.' + @col_redef
+ ');'
--Write out the parameter direction it is output
IF(@col_isout=1)
BEGIN
PRINT ' Parameters['+ cast(@col_order-1
as varchar)
+'].Direction=ParameterDirection.Output;'
SET @outputValues=@outputValues+'
Parameters['+cast(@col_order-1 as varchar) +'].Value;'
END
ELSE
BEGIN
--Write out the parameter value line
PRINT ' Parameters['+ cast(@col_order-1
as varchar) + '].Value = ?;'
END
--If the type is a string then output the size declaration
IF(@col_xtype=231)OR(@col_xtype=167)OR(@col_xtype=175)OR(
@col_xtype=99)OR(@col_xtype=35)
BEGIN
PRINT ' Parameters[' + cast(
@col_order-1 as varchar) +
'].Size=' + cast(@col_len as varchar) + ';'
END
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM cur INTO
@source_name,@source_type,@col_name,@col_order,
@col_len,@col_key,@col_isout,@col_xtype
END
PRINT ''
print ' com.Parameters.AddRange(Parameters);'
if @isexcute = 0 --使用的execute方法執(zhí)行sql語句
begin
print 'try
{
conn.Open();
com.ExecuteNonQuery();
}
catch (Exception ee)
{
throw ee;
}
finally
{
conn.Close();
}'
end
else if @isexcute = 1--需要返回數(shù)據(jù)集的話使用這個
begin
print 'try
{
da.Fill(ds);
}
catch (Exception ee)
{
throw ee;
}
finally
{
//do what you want to do or dispose resoures.
}'
end
CLOSE cur
DEALLOCATE cur
END
if(LEN(@errMsg)>0) PRINT @errMsg
DROP TABLE #t_obj
SET NOCOUNT ON
用.Net兩年了,也積累了一些知識和經(jīng)驗,覺得應(yīng)該做出點自己的東西,而并不只是給別人打工。
所以決定利用最新發(fā)布的VS2008(Orcas) Beta2也加入到WEB 2.0的大潮中來,一來是學(xué)以所用,二來在實踐中掌握最新的技術(shù)。
現(xiàn)在流行在開發(fā)階段給項目起個Code Name,我也來湊湊熱鬧,就叫Pluto,以紀(jì)念不久前被剝奪九大行星資格的我們天蝎座的守護(hù)星——冥王星
平時有自己的工作,只能利用不多的業(yè)余時間開發(fā),所以預(yù)計(爭?。┰赩S2008正式發(fā)布之際,Pluto也能開發(fā)完成。
在這里,我會記錄下開發(fā)Pluto中的一些事情。
WEB 2.0的網(wǎng)站少不了數(shù)據(jù)庫、數(shù)據(jù)訪問,也是一切操作之本,而VS 2008中最大的亮點之一Linq也恰巧是做這個的,所以我的開發(fā)從Linq、從數(shù)據(jù)庫開始。網(wǎng)上關(guān)于Linq的教學(xué)鋪天蓋地,我不準(zhǔn)備重復(fù),我只寫下我遇到的問題。
Linq,更新數(shù)據(jù)怎么就那么費勁?
Linq的全稱是Language Integrated Query,也就是說Linq是以一個查詢語言的方式出現(xiàn)在我們面前的。在查詢方面Linq做了不少的優(yōu)化,我們不用在費盡心思去拼裝SQL語句、組裝實體等,所有操作在Linq里都是強(qiáng)類型的,我們用C#代碼輕松地寫出漂亮的SQL語句。
那么做為一個查詢語言,Linq在數(shù)據(jù)更新方面又是怎么表現(xiàn)的呢?通常來說Linq的更新會以以下的方式出現(xiàn)(絕大部分教程中都是這么寫的)
1var ctx = new MyDataContext();
2var user = ctx.Users.Where(u => u.UserId == userId).Single();
3user.UserName = "New User Name";
4ctx.SubmitChanges();
這些是C#代碼,但是背后做了什么呢?Linq會為我們生成類似一下的SQL語句
1--第一步,查詢
2SELECT UserId, UserName, FirstName, LastName, CreatTime From User WHERE UserId = @userId
3
4--第二部,更新
5UPDATE User SET UserName = @newUserName
6WHERE UserId = @oldUserId, userName = @oldUserName, FirstName = @oldFirstName, LastName = @oldLastName
發(fā)現(xiàn)了什么?首先Linq會取出所有的字段,在user.UserName = "New User Name"的時候,記錄下UserName字段被更新過了,UPDATE時會只更新UserName,但是把之前所有字段的值放在WHERE語句里來做為條件。
Are you kidding?! 這樣的效率實在是太差了吧?!
拋開效率問題,接下來我們看另外一種更新,有個某個字段記錄頁面被訪問的次數(shù),平時我們會用
1UPDATE POST SET Views = Views + 1 WHERE PostId = @PostId
但是如果我們寫下如下C#代碼
1var ctx = MyDataContext();
2var post = ctx.Posts.Where(p => p.PostId = @postId).Single();
3post.Views++
4ctx.SubmitChanges();
SQLDMO(SQL Distributed ManagementObjects,SQL分布式管理對象)封裝了Microsoft SQL Server數(shù)據(jù)庫中的對象。SQLDMO是Microsoft SQLServer中企業(yè)管理器所使用的應(yīng)用程序接口,所以它可以執(zhí)行很多功能,其中當(dāng)然也包括對數(shù)據(jù)庫的備份和恢復(fù)。
SQLDMO由Microsoft SQL Server自帶的SQLDMO.dll提供,由于SQLDMO.dll是一個COM對象,所以大家在用之前必須在.NET項目中添加對它的引用
下面是用C#語言書寫的用于Microsoft SQL Server數(shù)據(jù)庫備份和恢復(fù)的類:
using System;
namespace DbService
{
/// <summary>
/// DbOper類,主要應(yīng)用SQLDMO實現(xiàn)對Microsoft SQL Server數(shù)據(jù)庫的備份和恢復(fù)
/// </summary>
public sealed class DbOper
{
/// <summary>
/// DbOper類的構(gòu)造函數(shù)
/// </summary>
private DbOper()
{
}
/// <summary>
/// 數(shù)據(jù)庫備份
/// </summary>
public static void DbBackup()
{
SQLDMO.Backup oBackup = new SQLDMO.BackupClass();
SQLDMO.SQLServer oSQLServer = new SQLDMO.SQLServerClass();
try
{
oSQLServer.LoginSecure = false;
oSQLServer.Connect("localhost", "sa", "1234");
oBackup.Action = SQLDMO.SQLDMO_BACKUP_TYPE.SQLDMOBackup_Database;
oBackup.Database = "Northwind";
oBackup.Files = @"d:Northwind.bak";
oBackup.BackupSetName = "Northwind";
oBackup.BackupSetDescription = "數(shù)據(jù)庫備份";
oBackup.Initialize = true;
oBackup.SQLBackup(oSQLServer);
}
catch
{
throw;
}
finally
{
oSQLServer.DisConnect();
}
}
/// <summary>
/// 數(shù)據(jù)庫恢復(fù)
/// </summary>
public static void DbRestore()
{
SQLDMO.Restore oRestore = new SQLDMO.RestoreClass();
SQLDMO.SQLServer oSQLServer = new SQLDMO.SQLServerClass();
try
{
oSQLServer.LoginSecure = false;
oSQLServer.Connect("localhost", "sa", "1234");
oRestore.Action = SQLDMO.SQLDMO_RESTORE_TYPE.SQLDMORestore_Database;
oRestore.Database = "Northwind";
oRestore.Files = @"d:Northwind.bak";
oRestore.FileNumber = 1;
oRestore.ReplaceDatabase = true;
oRestore.SQLRestore(oSQLServer);
}
catch
{
throw;
}
finally
{
oSQLServer.DisConnect();
}
}
}
}
這段代碼雖然很短,但是卻很實用,希望能夠?qū)Υ蠹矣兴鶐椭?)
簡單地說,LINQ 是支持以類型安全方式查詢數(shù)據(jù)的一系列語言擴(kuò)展;它將在代號為“Orcas”的下一個版本 VisualStudio 中發(fā)布。待查詢數(shù)據(jù)的形式可以是 XML(LINQ 到 XML)、數(shù)據(jù)庫(啟用 LINQ 的 ADO.NET,其中包括 LINQ 到SQL、LINQ 到 Dataset 和 LINQ 到 Entities)和對象 (LINQ 到 Objects) 等。LINQ 體系結(jié)構(gòu)如圖1 所示。
圖 1 LINQ 體系結(jié)構(gòu)
讓我們看一些代碼。在即將發(fā)布的“Orcas”版 C# 中,LINQ 查詢可能如下所示:
var overdrawnQuery = from account in db.Accounts
where account.Balance < 0
select new { account.Name, account.Address };
當(dāng)使用 foreach 遍歷此查詢的結(jié)果時,返回的每個元素都將包含一個余額小于 0 的帳戶的名稱和地址。
從以上示例中立即可以看出該語法類似于 SQL。幾年前,Anders Hejlsberg(C# 的首席設(shè)計師)和 Peter Golde曾考慮擴(kuò)展 C# 以更好地集成數(shù)據(jù)查詢。Peter 時任 C# 編譯器開發(fā)主管,當(dāng)時正在研究擴(kuò)展 C# 編譯器的可能性,特別是支持可驗證SQL 之類特定于域的語言語法的加載項。另一方面,Anders 則在設(shè)想更深入、更特定級別的集成。他當(dāng)時正在構(gòu)思一組“序列運算符”,能在實現(xiàn)IEnumerable 的任何集合以及實現(xiàn) IQueryable 的遠(yuǎn)程類型查詢上運行。最終,序列運算符的構(gòu)思獲得了大多數(shù)支持,并且Anders 于 2004 年初向比爾·蓋茨的 Thinkweek遞交了一份關(guān)于本構(gòu)思的文件。反饋對此給予了充分肯定。在設(shè)計初期,簡單查詢的語法如下所示:
sequence locals = customers.where(ZipCode == 98112);
在此例中,Sequence 是 IEnumerable 的別名;“where”一詞是編譯器能理解的一種特殊運算符。Where運算符的實現(xiàn)是一種接受 predicate 委托(即 bool Pred(T item) 形式的委托)的普通 C#靜態(tài)方法。本構(gòu)思的目的是讓編輯器具備與運算符有關(guān)的特殊知識。這樣將允許編譯器正確調(diào)用靜態(tài)方法并創(chuàng)建代碼,將委托與表達(dá)式聯(lián)系起來。
假設(shè)上述示例是 C# 的理想查詢語法。在沒有任何語言擴(kuò)展的情況下,該查詢在 C# 2.0 中又會是什么樣子?
IEnumerable locals = EnumerableExtensions.Where(customers,delegate(Customer c)
{
return c.ZipCode == 98112;
});
這個代碼驚人地冗長,而且更糟糕的是,需要非常仔細(xì)地研究才能找到相關(guān)的篩選器 (ZipCode ==98112)。這只是一個簡單的例子;試想一下,如果使用數(shù)個篩選器、投影等,要讀懂代碼該有多難。冗長的根源在于匿名方法所要求的語法。在理想的查詢中,除了要計算的表達(dá)式,表達(dá)式不會提出任何要求。隨后,編譯器將嘗試推斷上下文;例如,ZipCode 實際上引用了 Customer 上定義的ZipCode。如何解決這一問題?將特定運算符的知識硬編碼到語言中并不能令語言設(shè)計團(tuán)隊滿意,因此他們開始為匿名方法尋求替代語法。他們要求該語法應(yīng)極其簡練,但又不必比匿名方法當(dāng)前所需的編譯器要求更多的知識。最終,他們發(fā)明了 lambda 表達(dá)式。
目前,基于數(shù)據(jù)庫服務(wù)器的桌面管理程序和Web程序已經(jīng)有太多的應(yīng)用了,尤其是網(wǎng)絡(luò)的大量普及,孤立地數(shù)據(jù)庫管理系統(tǒng)無法勝任分布式管理應(yīng)用,但是面對基于Access數(shù)據(jù)庫的現(xiàn)有的桌面應(yīng)用我們也無法完全的摒棄。我們利用.Net遠(yuǎn)程處理功能將連接和存取Access的行為封裝為一個遠(yuǎn)程對象,供網(wǎng)絡(luò)中其它客戶端通過調(diào)用該遠(yuǎn)程對象來存取實際的Access數(shù)據(jù)庫。我們以 C#2005 為開發(fā)語言來實現(xiàn)上述功能。
一、 技術(shù)要點
我們都知道Windows應(yīng)用程序在運行時會啟動一個進(jìn)程,其總包括若干線程,不同的進(jìn)程之間通信是開發(fā)分布式應(yīng)用程序所必需的,傳統(tǒng)上,這不僅需要深入了解通信流兩端上進(jìn)程的對象,而且還要深入了解低級別協(xié)議的宿主、應(yīng)用程序編程接口以及配置工具等??傊?,它是一項需要大量專業(yè)知識和經(jīng)驗的復(fù)雜任務(wù)。
幸好.Net為我們提供了遠(yuǎn)程處理功能,它所提供的通信方法可以快速而方便地完成上述建立通信的任務(wù)。因此,無論是需要快速開發(fā) Web應(yīng)用程序,還是要花費更多時間生成關(guān)鍵的企業(yè)范圍的應(yīng)用程序,.NET Framework 都會提供支持。通過 .NET遠(yuǎn)程處理,客戶端應(yīng)用程序可以使用同一臺計算機(jī)或其網(wǎng)絡(luò)中其他任何可用的計算機(jī)上的其他進(jìn)程中的對象。
要使用 .NET 遠(yuǎn)程處理創(chuàng)建可以讓兩個對象跨越應(yīng)用程序直接通信的應(yīng)用程序,只需生成以下對象即可:
1、 可遠(yuǎn)程處理的對象。
2、 偵聽對該遠(yuǎn)程對象的請求的應(yīng)用程序即服務(wù)器程序。
3、 對該遠(yuǎn)程對象發(fā)出請求的客戶端應(yīng)用程序。
.Net下不同應(yīng)用程序中的對象的通信方式有兩種:一種是跨應(yīng)用程序域邊界傳輸對象副本,一種是使用代理交換消息。MarshalByRefObject 是通過使用代理交換消息來進(jìn)行通信的對象的基類。當(dāng)跨應(yīng)用程序使用遠(yuǎn)程對象時,對象的基類必須是從MarshalByRefObject 繼承。
二、 程序?qū)崿F(xiàn)
?。?)我們先在VS的IDE中創(chuàng)建名為“TestRemoteAccess”的新的解決方案來容納前述用來實現(xiàn)遠(yuǎn)程處理的三個項目,首先向解決方案中添加名為“RemoteObject”的類庫,然后將默認(rèn)創(chuàng)建的類名更改為“CRemoteAccess”,并且繼承于“MarshalByRefObject”,代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
namespace RemoteObject
{
public class CRemoteAccess : MarshalByRefObject
{}
}
我們需要在該對象內(nèi)創(chuàng)建用于連接和存取本地Access數(shù)據(jù)庫的所有函數(shù),供服務(wù)端客戶端程序同時調(diào)用。用于連接和存取Access數(shù)據(jù)庫的方法這里不再詳述,參看附件源碼。
首先所有需要向客戶端公開的函數(shù)其可見性都必須設(shè)為 public。變量m_ConnString需要設(shè)置為publicstatic,目的是當(dāng)客戶端調(diào)用了SetRemoteAccessConnString后將數(shù)據(jù)庫連接字符串保存下來以備在本次連接期間始終能夠訪問,代碼如下:
……
public static string m_ConnString;
……
public void SetRemoteAccessConnString(string Connstr)
{
m_ConnString = Connstr;
}
……
成功連接了Access數(shù)據(jù)庫后我們需要返回數(shù)據(jù)集給請求的客戶端進(jìn)行顯示和編輯,在遠(yuǎn)程對象中我們聲明了幾個相關(guān)函數(shù):
private void LoadData(string SqlStr, string TableName)
public void SaveData(DataTable ClientDataTable)
public DataTable GetUserTable(string SqlStr, string TableName)
客戶端可以傳遞SQL查詢腳本通過調(diào)用GetUserTable來獲取相關(guān)數(shù)據(jù)庫表的數(shù)據(jù),并返回一個DataTable,然后可以將該DataTable附值給DataGridView以便將數(shù)據(jù)顯示出來。GetUserTable通過調(diào)用私有的LoadData函數(shù)來完成對數(shù)據(jù)的獲取。SaveData函數(shù)用于將編輯過的數(shù)據(jù)集保存回本地Access數(shù)據(jù)庫文件,代碼如下:
……
m_connection.Open();
m_adapter.Update(ClientDataTable);
……
?。?)遠(yuǎn)程對象創(chuàng)建完成,我們需要創(chuàng)建用于偵聽該遠(yuǎn)程對象請求的服務(wù)端應(yīng)用程序。在“TestRemoteAccess”解決方案中新建一個Windows窗體項目名為:“TestServer”,從工具箱中拖拽下幾個組件,界面如下所示:
由于最近和數(shù)據(jù)庫打交道,需要用C#和SQL Server 2005進(jìn)行操作,就把近段時間內(nèi)的最常用的操作做個總結(jié)。本人也是第一次用C#操作數(shù)據(jù)庫,所以這三種典型用法對初學(xué)者還是挺有幫助的。
以下是我在visual studio 2005上寫的一個類(連的是SQL Server 2005),已經(jīng)過測試通過。里面有3個方法比較典型,源碼如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace DatabaseOperate
{
class SqlOperateInfo
{
//Suppose your ServerName is "aa",DatabaseName is "bb",UserName is "cc", Password is "dd"
private string sqlConnectionCommand = "Data Source=aa;Initial Catalog=bb;User ID=cc;Pwd=dd";
//This table contains two columns:KeywordID int not null,KeywordName varchar(100) not null
private string dataTableName = "Basic_Keyword_Test";
private string storedProcedureName = "Sp_InertToBasic_Keyword_Test";
private string sqlSelectCommand = "Select KeywordID, KeywordName From Basic_Keyword_Test";
//sqlUpdateCommand could contain "insert" , "delete" , "update" operate
private string sqlUpdateCommand = "Delete From Basic_Keyword_Test Where KeywordID = 1";
public void UseSqlReader()
{
SqlConnection sqlConnection = new SqlConnection(sqlConnectionCommand);
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandType = System.Data.CommandType.Text;
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandText = sqlSelectCommand;
sqlConnection.Open();
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
while(sqlDataReader.Read())
{
//Get KeywordID and KeywordName , You can do anything you like. Here I just output them.
int keywordid = (int)sqlDataReader[0];
//the same as: int keywordid = (int)sqlDataReader["KeywordID"]
string keywordName = (string)sqlDataReader[1];
//the same as: string keywordName = (int)sqlDataReader["KeywordName"]
Console.WriteLine("KeywordID = " + keywordid + " , KeywordName = " + keywordName);
}
sqlDataReader.Close();
sqlCommand.Dispose();
sqlConnection.Close();
}
public void UseSqlStoredProcedure()
{
SqlConnection sqlConnection = new SqlConnection(sqlConnectionCommand);
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandText = storedProcedureName;
sqlConnection.Open();
sqlCommand.ExecuteNonQuery();
//you can use reader here,too.as long as you modify the sp and let it like select * from ....
sqlCommand.Dispose();
sqlConnection.Close();
}
public void UseSqlDataSet()
{
SqlConnection sqlConnection = new SqlConnection(sqlConnectionCommand);
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandType = System.Data.CommandType.Text;
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandText = sqlSelectCommand;
sqlConnection.Open();
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter();
sqlDataAdapter.SelectCommand = sqlCommand;
DataSet dataSet = new DataSet();
//sqlCommandBuilder is for update the dataset to database
SqlCommandBuilder sqlCommandBuilder = new SqlCommandBuilder(sqlDataAdapter);
sqlDataAdapter.Fill(dataSet, dataTableName);
//Do something to dataset then you can update it to Database.Here I just add a row
DataRow row = dataSet.Tables[0].NewRow();
row[0] = 10000;
row[1] = "new row";
dataSet.Tables[0].Rows.Add(row);
sqlDataAdapter.Update(dataSet, dataTableName);
sqlCommand.Dispose();
sqlDataAdapter.Dispose();
sqlConnection.Close();
}
}
}
以上的程序概括了最典型的用法,也是最基本的用法.更多的用法我將會陸續(xù)給出,大家有什么疑問或建議,歡迎來信(jiangbiao0827@163.com)或留言。
1.檢測到有潛在危險的 Request.Form 值
原因:
(1)在提交數(shù)據(jù)的頁面或webconfig中沒有對validateRequest的屬性進(jìn)行正確的設(shè)置
(2)HTML里面寫了兩個 引起解決:
方案一: 在.aspx文件頭中加入這句:
方案二: 修改web.config文件:
以下是引用片段:
<configuration>
<system.web>
<pages validateRequest="false" />
</system.web>
</configuration>
因為validateRequest默認(rèn)值為true。只要設(shè)為false即可。
2.“在沒有任何數(shù)據(jù)時進(jìn)行無效的讀取嘗試”解決辦法
原因:
所返回的sqldatareader無數(shù)據(jù)記錄,但沒有作記錄判斷力處理。返回的是空值
加上判斷即可: if (reader.read()) { TextName.Text =
reader["FieldName"].ToString(); }
3.數(shù)據(jù)為空。不能對空值調(diào)用此方法或?qū)傩浴?/p>
原因:
若對象是null,那么調(diào)用對象的方法例如ToString()肯定出錯一般是數(shù)據(jù)庫字段的值為空
在grideview等數(shù)據(jù)控件常出現(xiàn)
解決:因此建議作NULL處理
4.閱讀器關(guān)閉時 FieldCount 的嘗試無效
原因:
使用了SqlDataReader來綁定數(shù)據(jù)后,將connection對象作了Close()處理
類似
以下是引用片段:
public SqlDataReader GetSomething()
{
conn.open();
SqlDataReader reader =
sqlcmd.ExcecutReader(CommandBehavior.CloseConnection));
conn.close();// occur error here
return reader;
}
在綁定的時候調(diào)用了這個方法來指定數(shù)據(jù)源。如果使用這個方法則需要在調(diào)用函數(shù)中關(guān)閉Re
ader這樣conn就可以自動關(guān)閉。
如果是使用的是SqlDataAdapter和DataSet那么請去掉顯式關(guān)閉conn的調(diào)用。或者在finally
中調(diào)用之。