Liu Guanglun

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  15 随笔 :: 1 文章 :: 4 评论 :: 0 引用

公告

用户经常提出两个问题:“我为什么要另外编写代码来使用内置于 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

  中调用之。

posted on 2010-08-31 21:55 Liu Guanglun 阅读(...) 评论(...) 编辑 收藏