用戶經常提出兩個問題:“我為什么要另外編寫代碼來使用內置于 Windows 中的功能?在框架中為什么沒有相應的內容可以為我完成這一任務?”當框架小組構建他們的 .NET 部分時,他們評估了為使 .NET 程序員可以使用 Win32 而需要完成的工作,結果發現 Win32 API 集非常龐大。他們沒有足夠的資源為所有 Win32 API 編寫托管接口、加以測試并編寫文檔,因此只能優先處理最重要的部分。許多常用操作都有托管接口,但是還有許多完整的 Win32 部分沒有托管接口。
平臺調用 (P/Invoke) 是完成這一任務的最常用方法。要使用 P/Invoke,您可以編寫一個描述如何調用函數的原型,然后運行時將使用此信息進行調用。另一種方法是使用 Managed Extensions to C++ 來包裝函數,這部分內容將在以后的專欄中介紹。
要理解如何完成這一任務,最好的辦法是通過示例。在某些示例中,我只給出了部分代碼;完整的代碼可以通過下載獲得。
簡單示例
在第一個示例中,我們將調用 Beep() API 來發出聲音。首先,我需要為 Beep() 編寫適當的定義。查看 MSDN 中的定義,我發現它具有以下原型:
BOOL Beep(
DWORD dwFreq, // 聲音頻率
DWORD dwDuration // 聲音持續時間
);
要用 C# 來編寫這一原型,需要將 Win32 類型轉換成相應的 C# 類型。由于 DWORD 是 4 字節的整數,因此我們可以使用 int 或 uint 作為 C# 對應類型。由于 int 是 CLS 兼容類型(可以用于所有 .NET 語言),以此比 uint 更常用,并且在多數情況下,它們之間的區別并不重要。bool 類型與 BOOL 對應。現在我們可以用 C# 編寫以下原型:
public static extern bool Beep(int frequency, int duration);
這是相當標準的定義,只不過我們使用了 extern 來指明該函數的實際代碼在別處。此原型將告訴運行時如何調用函數;現在我們需要告訴它在何處找到該函數。 本文發表于www.bianceng.cn(編程入門網)
我們需要回顧一下 MSDN 中的代碼。在參考信息中,我們發現 Beep() 是在 kernel32.lib 中定義的。這意味著運行時代碼包含在 kernel32.dll 中。我們在原型中添加 DllImport 屬性將這一信息告訴運行時:
[DllImport("kernel32.dll")]
這就是我們要做的全部工作。下面是一個完整的示例,它生成的隨機聲音在二十世紀六十年代的科幻電影中很常見。
using System;
using System.Runtime.InteropServices;
namespace Beep
{
class Class1
{
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
static void Main(string[] args)
{
Random random = new Random();
for (int i = 0; i < 10000; i++)
{
Beep(random.Next(10000), 100);
}
}
}
}
它的聲響足以刺激任何聽者!由于 DllImport 允許您調用 Win32 中的任何代碼,因此就有可能調用惡意代碼。所以您必須是完全受信任的用戶,運行時才能進行 P/Invoke 調用。
枚舉和常量
Beep() 可用于發出任意聲音,但有時我們希望發出特定類型的聲音,因此我們改用 MessageBeep()。MSDN 給出了以下原型:
BOOL MessageBeep(
UINT uType // 聲音類型
);
這看起來很簡單,但是從注釋中可以發現兩個有趣的事實。
首先,uType 參數實際上接受一組預先定義的常量。
其次,可能的參數值包括 -1,這意味著盡管它被定義為 uint 類型,但 int 會更加適合。
對于 uType 參數,使用 enum 類型是合乎情理的。MSDN 列出了已命名的常量,但沒有就具體值給出任何提示。由于這一點,我們需要查看實際的 API。
如果您安裝了 Visual Studio? 和 C++,則 Platform SDK 位于 \Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\Include 下。
為查找這些常量,我在該目錄中執行了一個 findstr。
findstr "MB_ICONHAND" *.h
它確定了常量位于 winuser.h 中,然后我使用這些常量來創建我的 enum 和原型:
public enum BeepType
{
SimpleBeep = -1,
IconAsterisk = 0x00000040,
IconExclamation = 0x00000030,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
Ok = 0x00000000,
}
[DllImport("user32.dll")]
public static extern bool MessageBeep(BeepType beepType);
現在我可以用下面的語句來調用它: MessageBeep(BeepType.IconQuestion);
處理結構
有時我需要確定我筆記本的電池狀況。Win32 為此提供了電源管理函數。
搜索 MSDN 可以找到 GetSystemPowerStatus() 函數。
BOOL GetSystemPowerStatus(
LPSYSTEM_POWER_STATUS lpSystemPowerStatus
);
此函數包含指向某個結構的指針,我們尚未對此進行過處理。要處理結構,我們需要用 C# 定義結構。我們從非托管的定義開始:
typedef struct _SYSTEM_POWER_STATUS {
BYTE ACLineStatus;
BYTE BatteryFlag;
BYTE BatteryLifePercent;
BYTE Reserved1;
DWORD BatteryLifeTime;
DWORD BatteryFullLifeTime;
} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS;
然后,通過用 C# 類型代替 C 類型來得到 C# 版本。
struct SystemPowerStatus
{
byte ACLineStatus;
byte batteryFlag;
byte batteryLifePercent;
byte reserved1;
int batteryLifeTime;
int batteryFullLifeTime;
}
這樣,就可以方便地編寫出 C# 原型:
[DllImport("kernel32.dll")]
public static extern bool GetSystemPowerStatus(
ref SystemPowerStatus systemPowerStatus);
在此原型中,我們用“ref”指明將傳遞結構指針而不是結構值。這是處理通過指針傳遞的結構的一般方法。
此函數運行良好,但是最好將 ACLineStatus 和 batteryFlag 字段定義為 enum:
enum ACLineStatus: byte
{
Offline = 0,
Online = 1,
Unknown = 255,
}
enum BatteryFlag: byte
{
High = 1,
Low = 2,
Critical = 4,
Charging = 8,
NoSystemBattery = 128,
Unknown = 255,
}
請注意,由于結構的字段是一些字節,因此我們使用 byte 作為該 enum 的基本類型。
字符串
雖然只有一種 .NET 字符串類型,但這種字符串類型在非托管應用中卻有幾項獨特之處。可以使用具有內嵌字符數組的字符指針和結構,其中每個數組都需要正確的封送處理。
在 Win32 中還有兩種不同的字符串表示:
ANSI
Unicode
最初的 Windows 使用單字節字符,這樣可以節省存儲空間,但在處理很多語言時都需要復雜的多字節編碼。Windows NT? 出現后,它使用雙字節的 Unicode 編碼。為解決這一差別,Win32 API 采用了非常聰明的做法。它定義了 TCHAR 類型,該類型在 Win9x 平臺上是單字節字符,在 WinNT 平臺上是雙字節 Unicode 字符。對于每個接受字符串或結構(其中包含字符數據)的函數,Win32 API 均定義了該結構的兩種版本,用 A 后綴指明 Ansi 編碼,用 W 指明 wide 編碼(即 Unicode)。如果您將 C++ 程序編譯為單字節,會獲得 A 變體,如果編譯為 Unicode,則獲得 W 變體。Win9x 平臺包含 Ansi 版本,而 WinNT 平臺則包含 W 版本。
由于 P/Invoke 的設計者不想讓您為所在的平臺操心,因此他們提供了內置的支持來自動使用 A 或 W 版本。如果您調用的函數不存在,互操作層將為您查找并使用 A 或 W 版本。
通過示例能夠很好地說明字符串支持的一些精妙之處。
簡單字符串
下面是一個接受字符串參數的函數的簡單示例:
BOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName, // 根路徑
LPDWORD lpSectorsPerCluster, // 每個簇的扇區數
LPDWORD lpBytesPerSector, // 每個扇區的字節數
LPDWORD lpNumberOfFreeClusters, // 可用的扇區數
LPDWORD lpTotalNumberOfClusters // 扇區總數
);
根路徑定義為 LPCTSTR。這是獨立于平臺的字符串指針。
由于不存在名為 GetDiskFreeSpace() 的函數,封送拆收器將自動查找“A”或“W”變體,并調用相應的函數。我們使用一個屬性來告訴封送拆收器,API 所要求的字符串類型。
以下是該函數的完整定義,就象我開始定義的那樣:
[DllImport("kernel32.dll")]
static extern bool GetDiskFreeSpace(
[MarshalAs(UnmanagedType.LPTStr)]
string rootPathName,
ref int sectorsPerCluster,
ref int bytesPerSector,
ref int numberOfFreeClusters,
ref int totalNumberOfClusters);
不幸的是,當我試圖運行時,該函數不能執行。問題在于,無論我們在哪個平臺上,封送拆收器在默認情況下都試圖查找 API 的 Ansi 版本,由于 LPTStr 意味著在 Windows NT 平臺上會使用 Unicode 字符串,因此試圖用 Unicode 字符串來調用 Ansi 函數就會失敗。
有兩種方法可以解決這個問題:一種簡單的方法是刪除 MarshalAs 屬性。如果這樣做,將始終調用該函數的 A 版本,如果在您所涉及的所有平臺上都有這種版本,這是個很好的方法。但是,這會降低代碼的執行速度,因為封送拆收器要將 .NET 字符串從 Unicode 轉換為多字節,然后調用函數的 A 版本(將字符串轉換回 Unicode),最后調用函數的 W 版本。
要避免出現這種情況,您需要告訴封送拆收器,要它在 Win9x 平臺上時查找 A 版本,而在 NT 平臺上時查找 W 版本。要實現這一目的,可以將 CharSet 設置為 DllImport 屬性的一部分:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
在我的非正式計時測試中,我發現這一做法比前一種方法快了大約百分之五。
對于大多數 Win32 API,都可以對字符串類型設置 CharSet 屬性并使用 LPTStr。但是,還有一些不采用 A/W 機制的函數,對于這些函數必須采取不同的方法。
字符串緩沖區
.NET 中的字符串類型是不可改變的類型,這意味著它的值將永遠保持不變。對于要將字符串值復制到字符串緩沖區的函數,字符串將無效。這樣做至少會破壞由封送拆收器在轉換字符串時創建的臨時緩沖區;嚴重時會破壞托管堆,而這通常會導致錯誤的發生。無論哪種情況都不可能獲得正確的返回值。 本文發表于www.bianceng.cn(編程入門網)
要解決此問題,我們需要使用其他類型。StringBuilder 類型就是被設計為用作緩沖區的,我們將使用它來代替字符串。下面是一個示例:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetShortPathName(
[MarshalAs(UnmanagedType.LPTStr)]
string path,
[MarshalAs(UnmanagedType.LPTStr)]
StringBuilder shortPath,
int shortPathLength);
使用此函數很簡單:
StringBuilder shortPath = new StringBuilder(80);
int result = GetShortPathName(@"d:\test.jpg", shortPath, shortPath.Capacity);
string s = shortPath.ToString();
請注意,StringBuilder 的 Capacity 傳遞的是緩沖區大小。
具有內嵌字符數組的結構
某些函數接受具有內嵌字符數組的結構。例如,GetTimeZoneInformation() 函數接受指向以下結構的指針:
typedef struct _TIME_ZONE_INFORMATION {
LONG Bias;
WCHAR StandardName[ 32 ];
SYSTEMTIME StandardDate;
LONG StandardBias;
WCHAR DaylightName[ 32 ];
SYSTEMTIME DaylightDate;
LONG DaylightBias;
} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;
在 C# 中使用它需要有兩種結構。一種是 SYSTEMTIME,它的設置很簡單:
struct SystemTime
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
這里沒有什么特別之處;另一種是 TimeZoneInformation,它的定義要復雜一些:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation
{
public int bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string standardName;
SystemTime standardDate;
public int standardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string daylightName;
SystemTime daylightDate;
public int daylightBias;
}
此定義有兩個重要的細節。第一個是 MarshalAs 屬性:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
查看 ByValTStr 的文檔,我們發現該屬性用于內嵌的字符數組;另一個是 SizeConst,它用于設置數組的大小。
我在第一次編寫這段代碼時,遇到了執行引擎錯誤。通常這意味著部分互操作覆蓋了某些內存,表明結構的大小存在錯誤。我使用 Marshal.SizeOf() 來獲取所使用的封送拆收器的大小,結果是 108 字節。我進一步進行了調查,很快回憶起用于互操作的默認字符類型是 Ansi 或單字節。而函數定義中的字符類型為 WCHAR,是雙字節,因此導致了這一問題。
我通過添加 StructLayout 屬性進行了更正。結構在默認情況下按順序布局,這意味著所有字段都將以它們列出的順序排列。CharSet 的值被設置為 Unicode,以便始終使用正確的字符類型。
經過這樣處理后,該函數一切正常。您可能想知道我為什么不在此函數中使用 CharSet.Auto。這是因為,它也沒有 A 和 W 變體,而始終使用 Unicode 字符串,因此我采用了上述方法編碼。
具有回調的函數
當 Win32 函數需要返回多項數據時,通常都是通過回調機制來實現的。開發人員將函數指針傳遞給函數,然后針對每一項調用開發人員的函數。
在 C# 中沒有函數指針,而是使用“委托”,在調用 Win32 函數時使用委托來代替函數指針。
EnumDesktops() 函數就是這類函數的一個示例:
BOOL EnumDesktops(
HWINSTA hwinsta, // 窗口實例的句柄
DESKTOPENUMPROC lpEnumFunc, // 回調函數
LPARAM lParam // 用于回調函數的值
);
HWINSTA 類型由 IntPtr 代替,而 LPARAM 由 int 代替。DESKTOPENUMPROC 所需的工作要多一些。下面是 MSDN 中的定義:
BOOL CALLBACK EnumDesktopProc(
LPTSTR lpszDesktop, // 桌面名稱
LPARAM lParam // 用戶定義的值
);
我們可以將它轉換為以下委托:
delegate bool EnumDesktopProc([MarshalAs(UnmanagedType.LPTStr)] string desktopName,int lParam);
完成該定義后,我們可以為 EnumDesktops() 編寫以下定義:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool EnumDesktops(
IntPtr windowStation,
EnumDesktopProc callback,
int lParam);
這樣該函數就可以正常運行了。
在互操作中使用委托時有個很重要的技巧:封送拆收器創建了指向委托的函數指針,該函數指針被傳遞給非托管函數。但是,封送拆收器無法確定非托管函數要使用函數指針做些什么,因此它假定函數指針只需在調用該函數時有效即可。
結果是如果您調用諸如 SetConsoleCtrlHandler() 這樣的函數,其中的函數指針將被保存以便將來使用,您就需要確保在您的代碼中引用委托。如果不這樣做,函數可能表面上能執行,但在將來的內存回收處理中會刪除委托,并且會出現錯誤。
其他高級函數
迄今為止我列出的示例都比較簡單,但是還有很多更復雜的 Win32 函數。下面是一個示例:
DWORD SetEntriesInAcl(
ULONG cCountOfExplicitEntries, // 項數
PEXPLICIT_ACCESS pListOfExplicitEntries, // 緩沖區
PACL OldAcl, // 原始 ACL
PACL *NewAcl // 新 ACL
);
前兩個參數的處理比較簡單:ulong 很簡單,并且可以使用 UnmanagedType.LPArray 來封送緩沖區。
但第三和第四個參數有一些問題。問題在于定義 ACL 的方式。ACL 結構僅定義了 ACL 標頭,而緩沖區的其余部分由 ACE 組成。ACE 可以具有多種不同類型,并且這些不同類型的 ACE 的長度也不同。
如果您愿意為所有緩沖區分配空間,并且愿意使用不太安全的代碼,則可以用 C# 進行處理。但工作量很大,并且程序非常難調試。而使用 C++ 處理此 API 就容易得多。
屬性的其他選項
DLLImport 和 StructLayout 屬性具有一些非常有用的選項,有助于 P/Invoke 的使用。下面列出了所有這些選項:
DLLImport
CallingConvention
您可以用它來告訴封送拆收器,函數使用了哪些調用約定。您可以將它設置為您的函數的調用約定。通常,如果此設置錯誤,代碼將不能執行。但是,如果您的函數是 Cdecl 函數,并且使用 StdCall(默認)來調用該函數,那么函數能夠執行,但函數參數不會從堆棧中刪除,這會導致堆棧被填滿。
CharSet
控制調用 A 變體還是調用 W 變體。
EntryPoint
此屬性用于設置封送拆收器在 DLL 中查找的名稱。設置此屬性后,您可以將 C# 函數重新命名為任何名稱。
ExactSpelling
將此屬性設置為 true,封送拆收器將關閉 A 和 W 的查找特性。
PreserveSig
COM 互操作使得具有最終輸出參數的函數看起來是由它返回的該值。此屬性用于關閉這一特性。
SetLastError
確保調用 Win32 API SetLastError(),以便您找出發生的錯誤。
StructLayout
LayoutKind
結構在默認情況下按順序布局,并且在多數情況下都適用。如果需要完全控制結構成員所放置的位置,可以使用 LayoutKind.Explicit,然后為每個結構成員添加 FieldOffset 屬性。當您需要創建 union 時,通常需要這樣做。 本文發表于www.bianceng.cn(編程入門網)
CharSet
控制 ByValTStr 成員的默認字符類型。
Pack
設置結構的壓縮大小。它控制結構的排列方式。如果 C 結構采用了其他壓縮方式,您可能需要設置此屬性。
Size
設置結構大小。不常用;但是如果需要在結構末尾分配額外的空間,則可能會用到此屬性。
從不同位置加載
您無法指定希望 DLLImport 在運行時從何處查找文件,但是可以利用一個技巧來達到這一目的。
DllImport 調用 LoadLibrary() 來完成它的工作。如果進程中已經加載了特定的 DLL,那么即使指定的加載路徑不同,LoadLibrary() 也會成功。
這意味著如果直接調用 LoadLibrary(),您就可以從任何位置加載 DLL,然后 DllImport LoadLibrary() 將使用該 DLL。
由于這種行為,我們可以提前調用 LoadLibrary(),從而將您的調用指向其他 DLL。如果您在編寫庫,可以通過調用 GetModuleHandle() 來防止出現這種情況,以確保在首次調用 P/Invoke 之前沒有加載該庫。
P/Invoke 疑難解答
如果您的 P/Invoke 調用失敗,通常是因為某些類型的定義不正確。以下是幾個常見問題:
1.long != long。在 C++ 中,long 是 4 字節的整數,但在 C# 中,它是 8 字節的整數。
2.字符串類型設置不正確。
在Web編程中,我們常需要把一些本地文件上傳到Web服務器上,上傳后,用戶可以通過瀏覽器方便地瀏覽這些文件,應用十分廣泛。
那么使用C#如何實現文件上傳的功能呢?下面筆者簡要介紹一下。
首先,在你的Visual C# web project 中增加一個上傳用的Web Form,為了要上傳文件,需要在ToolBox中選擇HTML類的File Field控件,將此控件加入到Web Form中,然而此時該控件還不是服務端控件,我們需要為它加上如下一段代碼:
<form method=post encType=multipart/ form-data runat="server">
,這樣它就成為服務端控件了,如果需要同時上傳數個文件時,我們可以相應增加此控件。
需要注意的是代碼中一定要把 的屬性設置成為:
<form method=post encType=multipart/ form-data runat="server">
如果沒有這個屬性,就不能實現上傳。
然后在此Web Form中增加一個Web Form類的Button,雙擊Button添加如下代碼:
//上傳圖片的程序段
DateTime now = DateTime.Now ;
//取現在時間到DataTime類的對象now中
string strBaseLocation = "D:\web\FC\pic\";
//這是文件將上傳到的服務器的絕對目錄
if (uploadfile1.PostedFile.ContentLength != 0)
//判斷選取對話框選取的文件長度是否為0
{
uploadfile1.PostedFile.SaveAs(strBaseLocation+now.
DayOfYear.ToString()+uploadfile1.PostedFile.
ContentLength.ToString()+".jpg");
//執行上傳,并自動根據日期和文件大小不同為文件命名,確保不重復
Label1.Text="圖片1已經上傳,文件名為:"+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() ;
}
上面的代碼用于筆者開發的一個使用XML文件存儲新聞信息的系統中,后面幾句代碼作用是寫上傳文件信息到XML文件中。如果要上傳其他類型文件,只需要將jpg改為相應類型的后綴名即可,如改為doc即可上傳Word文件,瀏覽器即可直接瀏覽上傳的Word文件。
【注意事項】
1. 上傳文件不可以無限大;
2. 要注意IIS的安全性方面的配合;
3. 用Visual Studio 的安裝項目做安裝程序的時候,請注意安裝程序所在的絕對路徑問題;
4. 注意文件上傳后的重名問題。
簡介
僅僅使用一行簡單的程序,你就能夠使你的Windows窗體的所有菜單和上下文菜單具有office2003的菜單外觀。同樣地,你也可以只用一行程序,就能為你的菜單加上漂亮的圖標。本文實現的是一個具有該功能的組件。如果你想讓你的菜單恢復原來的外觀,也只須調用End方法即可。
組件的使用
要正確使用組件,必須先將你的組件加入到工具箱中。然后將該組件從工具箱中拖放放到form窗體中。這時會看到你的form的設計頁中多出了一個名為OfficeMenus1的圖標,說明已經將菜單組件加入到了form中。緊接著調用如下方法:
//開始顯示office 2003菜單
OfficeMenus1.Start( FormName ); 注:FormName為要改變菜單風格的窗口名稱。同樣,你也可以通過調用如下方法終止菜單的office2003風格,使之回到原始外觀: // 改變菜單的外觀風格到原始狀態
OfficeMenus1.End();為菜單頂添加圖標也很簡單,只須為工程添加一個ImageList(圖像列表控件),然后將OfficeMenu組件的ImageList屬性更改為你添加的ImageList,使用如下代碼實現: // 為菜單添加圖像
// OfficeMenus.AddPicture( MenuItem MenuItemToAddPictureTo, int ImageIndex );
OfficeMenus1.ImageList = imageList1;
OfficeMenus1.AddPicture(menuItem2, 1);
可以看出,只須如此幾行代碼就能輕松讓你的菜單實現office2003風格。
組件的實現方法及原理
組件由三個類實現,這三個類分別為OfficeMenus,MainMenuItemDrawing和MenuItemDrawing,都位于命名空間Dev4Arabs中。由于實現代碼較長,所以在此只給出了組件實現的思路。
組件實現的第一步是從System.ComponentModel.Component類派生類OfficeMenus。定義如下: public class OfficeMenus : System.ComponentModel.Component然后在類中定義兩靜態變量:
//圖像列表用來存儲菜單中用到的圖標 static ImageList _imageList;// 存儲圖片細節的一個名稱集合,NameValueCollection的詳細說明請查閱MSDN,該類主要用來使每個菜單的句柄與每個圖標形成一一對應的關系,以便后面繪制菜單頂的圖標時快速地找到某個菜單所對應的圖標。 static NameValueCollection picDetails = new NameValueCollection();接下來定義公
ODP.NET 11g是Oracle發布的供.NET程序訪問Oracle數據庫的ADO.NET組件,比微軟自帶的Oracle組件性能好,更可以訪問UDT(User Defined Type)類型,Procedure,REF等等高級Oracle特性。
.NET 1.1的客戶端需要的發布文件如下:
◆Oracle.DataAccess.dll (odt111odp.netin1.x)
◆OraOps11.dll (odt111in)
.NET 2.0需要發布:
◆Oracle.DataAccess.dll (odt111odp.netin2.0)
◆OraOps11w.dll (odt111in)
上面的客戶端均需要OCI基本包支持:
◆oci.dll
◆oraociei11.dll (也可以用更小的oraociicus11.dll代替)
◆orannzsbb11.dll
為了在客戶端測試方便,還可以加上SQL*Plus包,包括兩個文件:
◆sqlplus.exe
◆orasqlplusic11.dll
發布sqlplus包可以使用sqlplus "user_name/password@//192.168.1.31:1521/ORCL"在客戶端測試Oracle的狀態。
根據上面原則,最小的ODP.NET Oracle客戶端發布文件包括5個文件,壓縮后大小為8MB:
◆oci.dll
◆oraociicus11.dll
◆orannzsbb11.dll
◆Oracle.DataAccess.dll
◆OraOps11.dll
概述
任何有實際價值的關系數據庫應用程序都離不開一大堆的查詢表。如果您是開發圖形用戶界面的專家,那么您知道這些查詢表將用于加工下拉列表框中的列表。我將查詢表分成兩種:只讀表和可改寫只讀表。二者的區別在于什么會導致表的改變。我認為如果需要召開員工會議或者用戶會議才可以修改表的內容,那么表就是只讀的。一個好的例子就是公司的產品類別表。表的內容將不會改變直到公司研發并向市場投放了新產品,或者公司進行了重組。可改寫的只讀表是內容相對固定的列表,但可以被最終用戶修改,通常使用組合框而不用下拉列表框來展現。可改寫只讀表的一個例子就是稱謂術語表。應用程序設計人員能夠考慮到最常用的那些稱謂,如Ms., Mr., Mrs.以及Dr.,但總有某個用戶的頭銜是您從未考慮過的而且希望把它添加進來。舉個例子看看這種情況有多么常見,我最后工作的一個中型產品有一個設計良好符合第三范式的關系數據庫,它包含了350—400張表,我估計了一下大約有250張表是只讀的或可改寫只讀表。
傳統的Web應用程序(三層結構應用程序的經典范例)希望盡可能地緩存這種類型的表。這樣不僅可以減少往返數據庫的次數,還可以降低數據庫服務器的查詢負載、提高某些應用場景的響應能力,例如接受了新訂單。只讀表很容易緩存;可以始終將表放在緩存中,偶爾需要重新加載表時,則由數據庫管理員(DBA)通過某種方式重新加載緩存。我希望您的企業中很少召開對數據庫基本結構和內容作修改的會議。重新刷新中間層緩存中可改寫只讀表則有一點點麻煩。如果緩存刷新的頻率太低就無法獲得您期望的效果;用戶也無法立刻看到其它用戶對數據的修改。支持人員可以使用另一個應用程序添加新項,然后給打算使用該項的同伴發送一條即時消息,但同伴的選擇列表框中卻并不包含新添加的項。更糟糕的是,如果第二個用戶此時試圖重新添加這條“缺失的列表項”,就會收到一條數據庫錯誤告知該項已經存在了。由于類似問題的存在,如果可改寫只讀表允許多點更新,那么通常不進行緩存。
過去,程序員不得不自己手工開發解決方案,使用消息隊列、進行文件輸出的觸發器、或者out-of-band協議來通知緩存,如果應用程序外部的某些用戶更新了可改寫只讀表。這種“通知”解決方案只是通知緩存有行被添加或修改了,指示必須刷新緩存。如何通知緩存有哪些行改變了或者有哪些行被添加了,這一直都是個難題,是分布式數據庫和分布式事務或者合并復制領域的問題。低成本的通知解決方案的做法是:只要程序收到“緩存無效“的消息就刷新整個緩存。
SqlDependency 提供了緩存解決方案
如果您正在使用SQL Server 2005和ADO.NET 2.0,那么一種新的稱為查詢通知的通知解決方案已內置在SqlClient數據供應商和數據庫中。該問題終于有了內置且易于使用的解決方案!ASP.NET 2.0也內置了用于支持查詢通知的特性。ASP.NET的Cache對象可以注冊通知,甚至還可以將通知與ASP.NET的頁面緩存或頁面片段緩存結合在一起使用。
實現該功能的架構中包含了SQL Server 2005查詢引擎、SQL Server Service Broker、sp_DispatcherProc系統存儲過程,ADO.NET的SqlNotification (System.Data.Sql.SqlNotificationRequest)和SqlDependency類(System.Data.SqlClient.SqlDependency),以及ASP.NET的Cache類(System.Web.Caching.Cache)簡而言之,它的工作方式如下:
1.每個ADO.NET SqlCommand都包含一個Notification屬性,表示對通知的請求。
當執行SqlCommand時,如果存在Notification屬性,那么就會在網絡請求中附加一個表示通知請求的網絡協議包(TDS)。
2.SQL Server使用查詢通知架構注冊所請求通知的訂閱,然后執行命令。
3.SQL Server“監視”任何可能導致最初返回的結果集發生變化的SQL DML語句。當發生變生時,就向Service Broker服務發送消息。
4.消息可以:
a.引發通知并將通知傳回注冊的客戶端。
b.駐留在Service Broker's的服務隊列中,高級客戶端可以實現自己的處理機制。

圖1. 通知服務概覽
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方法執行sql語句
begin
print 'try
{
conn.Open();
com.ExecuteNonQuery();
}
catch (Exception ee)
{
throw ee;
}
finally
{
conn.Close();
}'
end
else if @isexcute = 1--需要返回數據集的話使用這個
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兩年了,也積累了一些知識和經驗,覺得應該做出點自己的東西,而并不只是給別人打工。
所以決定利用最新發布的VS2008(Orcas) Beta2也加入到WEB 2.0的大潮中來,一來是學以所用,二來在實踐中掌握最新的技術。
現在流行在開發階段給項目起個Code Name,我也來湊湊熱鬧,就叫Pluto,以紀念不久前被剝奪九大行星資格的我們天蝎座的守護星——冥王星
平時有自己的工作,只能利用不多的業余時間開發,所以預計(爭取)在VS2008正式發布之際,Pluto也能開發完成。
在這里,我會記錄下開發Pluto中的一些事情。
WEB 2.0的網站少不了數據庫、數據訪問,也是一切操作之本,而VS 2008中最大的亮點之一Linq也恰巧是做這個的,所以我的開發從Linq、從數據庫開始。網上關于Linq的教學鋪天蓋地,我不準備重復,我只寫下我遇到的問題。
Linq,更新數據怎么就那么費勁?
Linq的全稱是Language Integrated Query ,也就是說Linq是以一個查詢語言的方式出現在我們面前的。在查詢方面Linq做了不少的優化,我們不用在費盡心思去拼裝SQL語句、組裝實體等,所有操作在Linq里都是強類型的,我們用C#代碼輕松地寫出漂亮的SQL語句。
那么做為一個查詢語言,Linq在數據更新方面又是怎么表現的呢?通常來說Linq的更新會以以下的方式出現(絕大部分教程中都是這么寫的)
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
發現了什么?首先Linq會取出所有的字段,在user.UserName = "New User Name"的時候,記錄下UserName字段被更新過了,UPDATE時會只更新UserName,但是把之前所有字段的值放在WHERE語句里來做為條件。
Are you kidding?! 這樣的效率實在是太差了吧?!
拋開效率問題,接下來我們看另外一種更新,有個某個字段記錄頁面被訪問的次數,平時我們會用
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 Management Objects,SQL分布式管理對象)封裝了Microsoft SQL Server數據庫中的對象。SQLDMO是Microsoft SQL Server中企業管理器所使用的應用程序接口,所以它可以執行很多功能,其中當然也包括對數據庫的備份和恢復。
SQLDMO由Microsoft SQL Server自帶的SQLDMO.dll提供,由于SQLDMO.dll是一個COM對象,所以大家在用之前必須在.NET項目中添加對它的引用
下面是用C#語言書寫的用于Microsoft SQL Server數據庫備份和恢復的類:
using System;
namespace DbService
{
/// <summary>
/// DbOper類,主要應用SQLDMO實現對Microsoft SQL Server數據庫的備份和恢復
/// </summary>
public sealed class DbOper
{
/// <summary>
/// DbOper類的構造函數
/// </summary>
private DbOper()
{
}
/// <summary>
/// 數據庫備份
/// </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 = "數據庫備份";
oBackup.Initialize = true;
oBackup.SQLBackup(oSQLServer);
}
catch
{
throw;
}
finally
{
oSQLServer.DisConnect();
}
}
/// <summary>
/// 數據庫恢復
/// </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();
}
}
}
}
這段代碼雖然很短,但是卻很實用,希望能夠對大家有所幫助:)
簡單地說,LINQ 是支持以類型安全方式查詢數據的一系列語言擴展;它將在代號為“Orcas”的下一個版本 Visual Studio 中發布。待查詢數據的形式可以是 XML(LINQ 到 XML)、數據庫(啟用 LINQ 的 ADO.NET,其中包括 LINQ 到 SQL、LINQ 到 Dataset 和 LINQ 到 Entities)和對象 (LINQ 到 Objects) 等。LINQ 體系結構如圖 1 所示。

圖 1 LINQ 體系結構
讓我們看一些代碼。在即將發布的“Orcas”版 C# 中,LINQ 查詢可能如下所示:
var overdrawnQuery = from account in db.Accounts
where account.Balance < 0
select new { account.Name, account.Address };
當使用 foreach 遍歷此查詢的結果時,返回的每個元素都將包含一個余額小于 0 的帳戶的名稱和地址。
從以上示例中立即可以看出該語法類似于 SQL。幾年前,Anders Hejlsberg(C# 的首席設計師)和 Peter Golde 曾考慮擴展 C# 以更好地集成數據查詢。Peter 時任 C# 編譯器開發主管,當時正在研究擴展 C# 編譯器的可能性,特別是支持可驗證 SQL 之類特定于域的語言語法的加載項。另一方面,Anders 則在設想更深入、更特定級別的集成。他當時正在構思一組“序列運算符”,能在實現 IEnumerable 的任何集合以及實現 IQueryable 的遠程類型查詢上運行。最終,序列運算符的構思獲得了大多數支持,并且 Anders 于 2004 年初向比爾·蓋茨的 Thinkweek 遞交了一份關于本構思的文件。反饋對此給予了充分肯定。在設計初期,簡單查詢的語法如下所示:
sequence locals = customers.where(ZipCode == 98112);
在此例中,Sequence 是 IEnumerable 的別名;“where”一詞是編譯器能理解的一種特殊運算符。Where 運算符的實現是一種接受 predicate 委托(即 bool Pred(T item) 形式的委托)的普通 C# 靜態方法。本構思的目的是讓編輯器具備與運算符有關的特殊知識。這樣將允許編譯器正確調用靜態方法并創建代碼,將委托與表達式聯系起來。
假設上述示例是 C# 的理想查詢語法。在沒有任何語言擴展的情況下,該查詢在 C# 2.0 中又會是什么樣子?
IEnumerable locals = EnumerableExtensions.Where(customers,delegate(Customer c)
{
return c.ZipCode == 98112;
});
這個代碼驚人地冗長,而且更糟糕的是,需要非常仔細地研究才能找到相關的篩選器 (ZipCode == 98112)。這只是一個簡單的例子;試想一下,如果使用數個篩選器、投影等,要讀懂代碼該有多難。冗長的根源在于匿名方法所要求的語法。在理想的查詢中,除了要計算的表達式,表達式不會提出任何要求。隨后,編譯器將嘗試推斷上下文;例如,ZipCode 實際上引用了 Customer 上定義的 ZipCode。如何解決這一問題?將特定運算符的知識硬編碼到語言中并不能令語言設計團隊滿意,因此他們開始為匿名方法尋求替代語法。他們要求該語法應極其簡練,但又不必比匿名方法當前所需的編譯器要求更多的知識。最終,他們發明了 lambda 表達式。
目前,基于數據庫服務器的桌面管理程序和Web程序已經有太多的應用了,尤其是網絡的大量普及,孤立地數據庫管理系統無法勝任分布式管理應用,但是面對基于Access數據庫的現有的桌面應用我們也無法完全的摒棄。我們利用.Net 遠程處理功能將連接和存取Access的行為封裝為一個遠程對象,供網絡中其它客戶端通過調用該遠程對象來存取實際的Access數據庫。我們以 C# 2005 為開發語言來實現上述功能。
一、 技術要點
我們都知道Windows應用程序在運行時會啟動一個進程,其總包括若干線程,不同的進程之間通信是開發分布式應用程序所必需的,傳統上,這不僅需要深入了解通信流兩端上進程的對象,而且還要深入了解低級別協議的宿主、應用程序編程接口以及配置工具等。總之,它是一項需要大量專業知識和經驗的復雜任務。
幸好.Net為我們提供了遠程處理功能,它所提供的通信方法可以快速而方便地完成上述建立通信的任務。因此,無論是需要快速開發 Web 應用程序,還是要花費更多時間生成關鍵的企業范圍的應用程序,.NET Framework 都會提供支持。通過 .NET 遠程處理,客戶端應用程序可以使用同一臺計算機或其網絡中其他任何可用的計算機上的其他進程中的對象。
要使用 .NET 遠程處理創建可以讓兩個對象跨越應用程序直接通信的應用程序,只需生成以下對象即可:
1、 可遠程處理的對象。
2、 偵聽對該遠程對象的請求的應用程序即服務器程序。
3、 對該遠程對象發出請求的客戶端應用程序。
.Net下不同應用程序中的對象的通信方式有兩種:一種是跨應用程序域邊界傳輸對象副本,一種是使用代理交換消息。MarshalByRefObject 是通過使用代理交換消息來進行通信的對象的基類。當跨應用程序使用遠程對象時,對象的基類必須是從 MarshalByRefObject 繼承。
二、 程序實現
(1)我們先在VS的IDE中創建名為“TestRemoteAccess”的新的解決方案來容納前述用來實現遠程處理的三個項目,首先向解決方案中添加名為“RemoteObject”的類庫,然后將默認創建的類名更改為“CRemoteAccess”,并且繼承于“MarshalByRefObject”,代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
namespace RemoteObject
{
public class CRemoteAccess : MarshalByRefObject
{}
}
我們需要在該對象內創建用于連接和存取本地Access數據庫的所有函數,供服務端客戶端程序同時調用。用于連接和存取Access數據庫的方法這里不再詳述,參看附件源碼。
首先所有需要向客戶端公開的函數其可見性都必須設為 public。變量m_ConnString需要設置為public static,目的是當客戶端調用了SetRemoteAccessConnString后將數據庫連接字符串保存下來以備在本次連接期間始終能夠訪問,代碼如下:
……
public static string m_ConnString;
……
public void SetRemoteAccessConnString(string Connstr)
{
m_ConnString = Connstr;
}
……
成功連接了Access數據庫后我們需要返回數據集給請求的客戶端進行顯示和編輯,在遠程對象中我們聲明了幾個相關函數:
private void LoadData(string SqlStr, string TableName)
public void SaveData(DataTable ClientDataTable)
public DataTable GetUserTable(string SqlStr, string TableName)
客戶端可以傳遞SQL查詢腳本通過調用 GetUserTable來獲取相關數據庫表的數據,并返回一個DataTable,然后可以將該DataTable附值給DataGridView以便將數據顯示出來。GetUserTable通過調用私有的LoadData 函數來完成對數據的獲取。SaveData函數用于將編輯過的數據集保存回本地Access數據庫文件,代碼如下:
……
m_connection.Open();
m_adapter.Update(ClientDataTable);
……
(2)遠程對象創建完成,我們需要創建用于偵聽該遠程對象請求的服務端應用程序。在“TestRemoteAccess”解決方案中新建一個Windows窗體項目名為:“TestServer”,從工具箱中拖拽下幾個組件,界面如下所示:
由于最近和數據庫打交道,需要用C#和SQL Server 2005進行操作,就把近段時間內的最常用的操作做個總結。本人也是第一次用C#操作數據庫,所以這三種典型用法對初學者還是挺有幫助的。
以下是我在visual studio 2005上寫的一個類(連的是SQL Server 2005),已經過測試通過。里面有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();
}
}
}
以上的程序概括了最典型的用法,也是最基本的用法.更多的用法我將會陸續給出,大家有什么疑問或建議,歡迎來信(jiangbiao0827@163.com)或留言。
1.檢測到有潛在危險的 Request.Form 值
原因:
(1)在提交數據的頁面或webconfig中沒有對validateRequest的屬性進行正確的設置
(2)HTML里面寫了兩個 引起
解決:
方案一: 在.aspx文件頭中加入這句:
方案二: 修改web.config文件:
以下是引用片段:
<configuration>
<system.web>
<pages validateRequest="false" />
</system.web>
</configuration>
因為validateRequest默認值為true。只要設為false即可。
2.“在沒有任何數據時進行無效的讀取嘗試”解決辦法
原因:
所返回的sqldatareader無數據記錄,但沒有作記錄判斷力處理。返回的是空值
加上判斷即可: if (reader.read()) { TextName.Text =
reader["FieldName"].ToString(); }
3.數據為空。不能對空值調用此方法或屬性。
原因:
若對象是null,那么調用對象的方法例如ToString()肯定出錯一般是數據庫字段的值為空
在grideview等數據控件常出現
解決:因此建議作NULL處理
4.閱讀器關閉時 FieldCount 的嘗試無效
原因:
使用了SqlDataReader來綁定數據后,將connection對象作了Close()處理
類似
以下是引用片段:
public SqlDataReader GetSomething()
{
conn.open();
SqlDataReader reader =
sqlcmd.ExcecutReader(CommandBehavior.CloseConnection));
conn.close();// occur error here
return reader;
}
在綁定的時候調用了這個方法來指定數據源。如果使用這個方法則需要在調用函數中關閉Re
ader這樣conn就可以自動關閉。
如果是使用的是SqlDataAdapter和DataSet那么請去掉顯式關閉conn的調用。或者在finally
中調用之。

浙公網安備 33010602011771號