万能数据库管理器






4.67/5 (13投票s)
我编写了一个类,
引言
处理数据库通常可能很枯燥。打开连接、检查连接是否正确、准备命令、执行、检查、检索和管理结果、关闭连接,所有这些都是我不想再重复的乏味操作。我构建了一个类,可以自动执行所有这些操作。而且它还可以做更多的事情。
注意
在附加的源代码中,有一个包含两个项目的解决方案。第一个项目(Database)包含我所说的那个类。第二个项目(Uso Database)包含一个控制台应用程序的主例程,演示如何使用第一个项目中的类。在该例程中,我使用了一些不同的连接来展示Database类的工作方式。为此,我浏览了一个SQL Server数据库和一个MySQL数据库,其中加载了标准的NorthWind内容。
您可以在这里找到生成Northwind内容的脚本下载
- SQL Server Northwind - 在此页面上有一个指向 MSI 安装程序的链接,该安装程序会将 SQL 脚本安装在 C:\Sql Server 2000 Sample Database 文件夹下。
- MySQL Northwind - 在此页面上有一个指向 SQL 脚本的链接。
SQL Server 脚本适用于 SQL Server 2000,但也能很好地用于 SQL Express 和更新的版本(我使用的是 SQL Express 2008,运行良好)。
如果您有其他版本的 SQL Server,或者禁用了 Windows 安全访问系统,您可能需要更改构造函数参数。
在 DatabaseExample.vb 的第三部分,您会找到对 SQL Server Compact Edition 数据库引擎的访问,该引擎无需任何安装,并且能够创建所需的架构而无需任何请求,并轻松地进行管理。如果您既没有 SQL Server 机器也没有 MySQL 机器,您可以直接测试此引擎,跳过其他两个。
在这种情况下,您可以看到附加文件 OutputBuffers.zip 中的控制台和调试器输出结果。
另一个说明:我用 VB.Net 编写了这个类。我更喜欢 VB.Net 而不是 C#,仅仅是因为它的错误报告更即时。如果有人尝试将其翻译成 C#,我将不胜感激,并且我可以验证您的翻译。如果您希望我来翻译,您可能要等很久 :-)
使用代码
您可以使用派生表的构造函数创建连接
' Connecting to SqlServer 2008 Express with Windows security access system enabled:
Using DB As DataBase = New DataBase.SQLServerDatabase(".\SqlExpress", "NorthWind")
' Connecting to MySql database installed in local machine and with user root and password adminpwd:
Using DB As DataBase = New DataBase.MySqlDatabase("127.0.0.1", "NorthWind", "root", "adminpwd")
无需打开连接,无需定义任何关于命令文本或其他任何内容。您可以忘记所有其他。此外,关闭连接不再是您的烦恼。连接将在超时后自动关闭,而无需任何命令。
下一个语句可能是请求查询
Using Result As DataBase.SQLResult = DB.Execute("Select * From Employees")
For Each DR As DataRow In Result.DataTable.Rows
[...]
Next DR
End Using
这将检查连接是否存在。如果不存在,它将被创建。然后它将被打开。接下来,它将创建命令文本、一个新的数据适配器、一个新的数据集,并最终填充。结果被放入一个 SQLResult 对象,并启动一个自动关闭计时器。SQLResult 对象知道最后一个 SELECT 命令的数据集,以及该数据集中的数据表 - 如果数据集只包含一个表或命令是架构请求 - 如果命令指定结果必须是 DataReader,SQLResult 对象将包含该 DataReader。此外,它还知道最后一次操作涉及的行数(在使用 DataReader 时),或者潜在的错误。
此外,还有一个函数可以为调试返回一个字符串(AsString()
),还有一个方法可以在发生错误时显示一个消息框。此方法具有可选参数,可用于选择显示的按钮、标题、用户定义的对话框消息,该消息还可以显示导致错误的 SQL 和/或堆栈位置。
完成。无需其他操作。很简单,对吧?
错误捕获也非常简单
Using Result As DataBase.SQLResult = DB.Execute("Select * From Employeees")
If Result.HasError Then
Result.ShowMsg()
Else
For Each DR As DataRow In Result.DataTable.Rows
[...]
Next DR
End If
End Using
或者,以一种更简单的方式
Using Result As DataBase.SQLResult = DB.Execute(True, "Select * From Employeees")
If Not Result.HasError Then
For Each DR As DataRow In Result.DataTable.Rows
[...]
Next DR
End If
End Using
参数 True
(ShowError
)将显示相同的消息框,包括导致错误的 SQL 命令。
另一种非常简单的错误捕获方法是检查是否有任何错误,并在语句结束时向用户发出警告。在以下示例中,我模拟插入一个没有产品 ID 的订单(这会引发错误,因为它不能为空)。
Dim DoneAs Boolean = True
DB.BeginTransaction()
Done = Done AndAlso Not DB.Execute("Insert Into Orders ({0}) Values ({1})",
New Dictionary(Of String, Object) From {{"OrderID", 11078},
{"CustomerID", "WARTH"},
{"EmployeeID", 5},
{"OrderDate", #7/4/1999#},
{"RequiredDate", #9/6/1999#},
{"ShipName", "Wartian Herkku"},
{"ShipAddress", "Torikatu 38"},
{"ShipCity", "Oulu"},
{"ShipPostalCode", "90110"},
{"ShipCountry", "Finland"}}).HasError
For Each W In {New With {.ID = 11078, .Prod = New Integer?(28),
.Price = 45.6, .Qty = 4, .Scount = 20},
New With {.ID = 11078, .Prod = New Integer?(12),
.Price = 38.0, .Qty = 6, .Scount = 20},
New With {.ID = 11078, .Prod = New Integer?(),
.Price = 32.45, .Qty = 1, .Scount = 20},
New With {.ID = 11078, .Prod = New Integer?(49),
.Price = 20.0, .Qty = 25, .Scount = 20}}
Done = Done AndAlso Not DB.Execute("Insert Into `Order details` ({0}) Values ({1})",
New Dictionary(Of String, Object) From {{"OrderID", W.ID},
{"ProductID", W.Prod},
{"UnitPrice", W.Price},
{"Quantity", W.Qty},
{"Discount", W.Scount}}).HasError
Next W
If Done Then
DB.Commit()
Else
DB.RollBack()
MsgBox("Unable to insert order" & vbCrLf & vbCrLf &
DB.LastException.Message & vbCrLf & vbCrLf & DB.LastSqlOnException)
End If
DoneAndAlso
强制程序仅在前面的命令没有错误的情况下执行 SQL 命令。第三行订单缺少产品 ID(在 Northwind 数据库中是必需的),这将导致异常。接下来的行将不会被插入!在循环结束时,连接将回滚订单插入命令,消息将显示异常的原因以及导致错误的 SQL。
如果在调用构造函数时将参数 Debugging:=True
,所有操作都将在调试器窗口中进行跟踪。例如,语句 X.Execute("Select * From Employees")
将在调试器窗口中打印以下行
17:41:04.832
Initialization
17:41:05.753
Connection Regenerated and opened
17:41:05.767
* * * Execute SelectString * * *
Command text:
Select * From dbo.Employees
17:41:05.888 (0,121)
Table (9 Rows)
-----------------------------------------------------------------------------------------------
EmployeeID|LastName |FirstName|Title |TitleOfCourtesy|BirthDate
Int32 |String |String |String |String |DateTime
-----------------------------------------------------------------------------------------------
1|Davolio |Nancy |Sales Representative |Ms. |12/08/1948 00:00:00.000
2|Fuller |Andrew |Vice President, Sales |Dr. |02/04/1952 00:00:00.000
3|Leverling|Janet |Sales Representative |Ms. |08/07/1963 00:00:00.000
4|Peacock |Margaret |Sales Representative |Mrs. |09/11/1937 00:00:00.000
5|Buchanan |Steven |Sales Manager |Mr. |03/04/1955 00:00:00.000
6|Suyama |Michael |Sales Representative |Mr. |07/02/1963 00:00:00.000
7|King |Robert |Sales Representative |Mr. |05/03/1960 00:00:00.000
8|Callahan |Laura |Inside Sales Coordinator|Ms. |01/09/1958 00:00:00.000
9|Dodsworth|Anne |Sales Representative |Ms. |10/01/1966 00:00:00.000
Connection closed
您可以使用相同的方法(.Execute)执行所有种类的查询。它通常会从第一个单词识别命令文本的调用类型。例如:如果命令字符串以 Select
命令开头,结果将放入数据集。如果以以下任何一个开头:Create
、Insert
、Update
、Delete
、Drop
、Alter
、Use
或 Set
,它将被视为 ExecutionNoQuery,返回的结果是命令涉及的行数 - 如果连接允许的话;我发现一些数据库引擎在所有情况下都返回 -1 :-(
如果命令字符串包含空格,它将被视为存储过程;在所有其他情况下,它将被视为表名,并且会执行类似 Select * From TheMentionedTable
的命令。在存在歧义的情况下,您可以在 Execute
方法中指定方法的行为。
DB.Execute(DataBase.CallType.Schema, "Tables", {Nothing, "dbo"})
除了构造函数,还有另一种创建派生类实例的方法。例如,您可以参数化数据库访问数据和使用的引擎。通过基类的 Build
方法,您可以参数化要使用的引擎。
For Each DBParameter In {New With {.DBType = DataBase.ConnectorTypeEnum.MySql,
.Server = "127.0.0.1", .DBName = "northwind",
.UID = "root", .Pwd = "adminpwd"},
New With {.DBType = DataBase.ConnectorTypeEnum.SqlServer,
.Server = ".\SqlExpress", .DBName = "NorthWind",
.UID = CStr(Nothing), .Pwd = CStr(Nothing)},
New With {.DBType = DataBase.ConnectorTypeEnum.SqlCompact,
.Server = MyFolder & "\DB.SclCEDB", .DBName = "",
.UID = "", .Pwd = "MyDataBasePwd"}}
Using DB = DataBase.Build(DBParameter.DBType, DBParameter.Server,
DBParameter.DBName, DBParameter.UID, DBParameter.Pwd, Debugging:=True)
DB.Execute("Select Count(*) from {0}",
DB.NamedColumn(If(DBParameter.DBType = DataBase.ConnectorTypeEnum.SqlCompact, "Users", "order details")))
End Using
Next DBParameter
实现不同的连接器
您可以继承主类来实现各种新的连接器。您需要做的就是继承该类,像我已经介绍过的连接器示例那样生成代码,将新类的描述包含在 ConnectorTypeEnum
中,并管理配置以在您需要使用 DataBase
类本身(无论是使用还是不使用新连接器)的项目中启用和禁用该新类(以及旧类)。
由于 build
方法使用反射,因此对新继承类的实现有一个强制性的要求:ConnectorTypeEnum
必须是一个新的枚举值,并且新继承的类名称必须组合该新值和后缀“Database”(例如,使用 MySql 连接器时,我创建了枚举值 ConnectorTypeEnum.MySql
,并将新实现的类命名为 MySqlDatabase
)。
我将不同的连接器放在不同的文件中,允许在使用不同连接器的项目中使用相同的文件,通过禁用或启用这些文件的引用。
为了实现不同的连接器,您需要将 Database 类继承到一个新文件中,并实现所有 MustOverride 方法,尤其要注意构造函数 - 其中设置了连接字符串和用于管理参数的所有变量 - 以及 AddParameters 方法:记住要在命令对象中使用正确的参数管理。您可以参考已包含的派生类作为示例。我在上传的项目中包含了五个不同的实现:SqlCompactEdition、SqlServer、MySql、Oracle 和 Odbc。最后一个是最通用的,因为它提供了一些可以在所有数据库引擎中使用的属性。如果您需要以非标准的方式管理某些内容,就可以体验到这一点,因为参数的前缀和后缀以及严格的对象命名变成了可读写而不是只读,并且有一种方法可以实现不同的转义字符串管理。SqlCompactEdition 展示了一种非常不同的数据库管理使用方式。RegenerateConnection
方法尝试打开数据库。如果失败,则创建或重新生成数据库文件(取决于错误的类型)。MySql 也展示了一种与标准不同的数据库管理方式。事实上,RegenerateConnection
方法设置了 net_write_timeout
和 net_read_timeout
参数,这些参数应该被设置以避免一些奇怪的致命错误。
之后,您需要通过 Visual Studio 的“编译”菜单中的“编辑配置”窗口创建一个新配置,选择“新建...”配置,然后手动编辑项目文件以包含或排除该文件,并通过选择不同的配置来启用或禁用对连接器 DLL 的引用。
这是两个不同实现的示例,第一个用于 SQL 和 MySQL 连接器,第二个用于 Oracle。您可以在项目文件中找到这些行。
<Choose>
<When Condition="'$(Configuration)' == 'SqlAndMySQL'">
<ItemGroup>
<Reference Include="MySql.Data, Version=6.6.5.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>C:\Program Files (x86)\MySQL\MySQL Connector Net 6.6.5\Assemblies\v4.0\MySql.Data.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ODBCDataBase.vb" />
<Compile Include="MySqlDatabase.vb" />
<Compile Include="SqlServerDatabase.vb" />
</ItemGroup>
</When>
<When Condition="'$(Configuration)' == 'SqlAndOracle'">
<ItemGroup>
<Reference Include="Oracle.DataAccess, Version=2.112.1.0, Culture=neutral, PublicKeyToken=89b483f429c47342, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>C:\Program Files\oracle\32\Oracle.DataAccess.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="OracleDatabase.vb" />
<Compile Include="SqlServerDatabase.vb" />
</ItemGroup>
</When>
</Choose>
背景
这一切都始于很久以前,当时我有两个不同的数据库引擎(Informix 和 SQL Server),它们拥有几乎相同的数据(尽管保存在名称不同的表和列中)。我开始编写一个类,以便在不知道连接类型本身的情况下打开连接。我的目标是能够有一个类,它只通过参数提供连接类型,就能使用正确的连接器、正确的命令和正确的数据适配器/数据读取器。
因此,我提供了将数据保存到字段中以访问数据库,并在需要时打开连接。然后,启动一个计时器,如果在另一个请求到来时,已打开的连接执行新的请求。否则,如果在指定时间内没有其他请求,连接将被关闭并处理。
之后,我开始像 String.Format
那样编译 SelectStrings
。在我的程序中,我开始将表名和列名存储在字典中,并像这样编写 SELECT 字符串:
"Select * From {0} Where {1} = 'Code'", TableDictionary!TableName, ColumnDictionary!Code
第二步是按照连接器要求格式化所有值
"Select * From {0} Where {1} = '{2}'", TableDictionary!TableName, ColumnDictionary!Code, MyValue
其中 MyValue
可以是包含任何字符的字符串。SQL Server 只需通过加倍引号字符来转义它们,Informix 需要在它们前面加上转义字符('\'),并且如果字符串中存在,则将其复制。
下一步是编写一个 PrepareString
方法,该方法可以检查所有参数,如果找到一个字符串类型的参数,则按照连接器要求进行转义。
PrepareString
方法并不像我想象的那么容易。如果参数是 DateTime,不同的数据库引擎有不同的字面表示。我开始添加开关来检查这些表示的日期时间类型。此外,我住在意大利,DateTime 的表示方式差异很大,无论我是想将值作为日期时间值放入,还是有一个列是字符串,其值可能是日期时间值(格式为 dd/MM/yyyy 而不是 MM/dd/yyyy)。我开始添加很多开关(后来被删除了)。考虑这两个语句
"Insert Into Table (Code, DateTime, Description) Values (10, '1/3/2001', 'The character ''\'' is a backslash')" "Insert Into Table (Code, DateTime, Description) Values (10, '2001-01-03-00-00-00', 'The character \'\\\' is a backslash')"
它们是同一 SQL 的不同表示,取决于连接器。我开始这样编写 SQL 命令:
"Insert Into Table ({0}, {1}, {2}) Values ({3}, '{4}', '{5}')", "Code", "DateTime", "Description", 10, #1/3/2001#, "The character '\' is a backslash"
这要好得多。下一步是使用参数。我找到了一种很好的使用方法。
考虑到
Dim Dk As New Dictionary(Of String, Object) From {{"Code", 10}, {"DateTime", #1/3/2001#}, {"Description", "The character '\' is a backslash"}} Execute("Insert Into Table ({0}) Values ({1})", Dk)
也可以这样使用
Dim Dk As New Dictionary(Of String, Object) From {{"Code", 10}, {"DateTime", #1/3/2001#}, {"Description", "The character '\' is a backslash"}} If Execute("Update Table set {0} Where Code = {1}", Dk, NamedParameter("Code")).RowNumber = 0 Then Execute("Insert Into Table ({0}) Values ({1})", Dk) End If
慢慢地,所有的用法都变得越来越优雅。但是每个连接器处理参数的方式却大不相同。这让我决定停止使用开关,开始使用不同的类。
此外,我在字典解释器中加入了一些不太常见的功能:第一个是关于 null。Null 值、空的可空值和 DbNull.Value
值都被解释为 DbNull 值。第二个是关于字符串。您可以将字段的长度放在字典的键中;这样,解析器将描述截断到该长度。因此,当您尝试将过长的字符串放入值时,可以避免错误或警告。
Dim Dk As New Dictionary(Of String, Object) From {{"Code", 10}, {"DateTime", #1/3/2001#}, {"Description(20)", "The character '\' is a backslash"}} If Execute("Update Table set {0} Where Code = {1}", Dk, NamedParameter("Code")).RowNumber = 0 Then Execute("Insert Into Table ({0}) Values ({1})", Dk) End If
解析器从名称中截断长度表示 (20)
,只留下 Description
。属性值将是 The character '\' is
。
另一个实现是允许批量插入和批量选择/删除。如果名称中存在 -
字符,它将被替换为参数命名中的 _
字符,并且解析器在检查该字符时会知道批量操作指向另一个行/选择。考虑到
Dim MyDataArray() = {New With {.Code = "One", .Art = "First",
.Descr = "Some description", .DT = Now},
New With {.Code = "Two", .Art = "Another",
.Descr = "Another too much long description that have to be cutted", .DT = Now.AddMonths(3)},
New With {.Code = "Three", .Art = "Last",
.Descr = "Last description", .DT = #3/5/1985 11:37:55 AM#}}
Dim DK As New Dictionary(Of String, Object)
Dim I As Integer = -1, Prefix As String = ""
For Each W In MyDataArray
DK.Add("Cod" & Prefix, W.Code)
DK.Add("Art" & Prefix, W.Art)
DK.Add("Descr(20)" & Prefix, W.Descr)
DK.Add("DT" & Prefix, W.DT)
I += 1
Prefix = I.ToString("\-00")
Next W
DB.Execute("Insert Into MyTable({0}) Values ({1})", DK)
DB.Execute("Delete From MyTable Where {0}", Dk)
它们被翻译成
Insert Into MyTable (Cod, Art, Descr, DT)
Values (@Cod, @Art, @Descr, @DT),
(@Cod_00, @Art_00, @Descr_00, @DT_00),
(@Cod_01, @Art_01, @Descr_01, @DT_01)
Delete From MyTable Where ((Cod = @Cod And Art = @Art And Descr = @Descr And DT = @DT) Or
(Cod = @Cod_00 And Art = @Art_00 And Descr = @Descr_00 And DT = @DT_00) Or
(Cod = @Cod_01 And Art = @Art_01 And Descr = @Descr_01 And DT = @DT_01))
即(使用连接器解析的字面日期)
Insert Into MyTable (Cod, Art, Descr, Dt)
Values ('One', 'First', 'Some description', Now),
('Two', 'Another', 'Another too much lon', Now + 3 Months),
('Three', 'Last', 'Last description', #3/5/1985#)
Delete From MyTable Where ((Cod = 'One' And Art = 'First' And Descr = 'Some description' And DT = Now) Or
(Cod = 'Two' And Art = 'Another' And Descr = 'Another too much lon' And DT = Now + 3 Months) Or
(Cod = 'Three' And Art = 'Last' And Descr = 'Last description' And DT = #3/5/1985#))
非常好。
现在我也开始使用 MySql、Oracle 和 SqlCompactEdition。实现它们非常容易。
该类实现的另一个功能是 Background Worker。当有一个延迟请求要在后台进程中执行时,ExecuteInBackground
方法会将所有请求存储在队列中,然后检查 BackgroundWorker 是否忙碌,如果不忙,它就用队列顶部的请求来填充 BackgroundWorker。当此请求结束时,进程将返回到前台线程,并调用最终要调用的方法。之后,它会检查队列是否清空,然后重新开始循环。
我使用 BackgroundWorker 有两个原因:第一个是我不知道哪种方法是最好的操作方式。起初,当执行 SqlCommand 时,它会调用 ProgressChange,而不会退出到后台线程,其中可以调用方法来在前台线程中调用。同时,后台线程继续执行队列中的下一个 SqlCommand。之后(正如后台工作程序设计的那样),当执行 SqlCommand 时,后台线程退出,进程返回到前台线程,调用要调用的方法,并重新检查队列。
最终我选择了这种设计。这样,外部方法就可以检查数据库以检查执行操作的结果。此外,我可以随时向队列请求添加内容(反之,队列必须被锁定,因为它是在后台线程中分析的,而前台线程可以为其提供)。它不如使用 AWait
优雅,但这样后台提供可以更有效。
考虑这个例子
DB.BeginWork()
DB.ExecuteInBackGround(ContinueCallBack, ArgumentList, "Create Temp Table MyTable ([...])")
DB.ExecuteInBackGround(ContinueCallBack, ArgumentList, "Insert Into MyTable ([...]) Select [...]")
DB.ExecuteInBackGround(ContinueCallBack, ArgumentList, "Update MyTable Set [...] Where [...]")
DB.ExecuteInBackGround(ContinueCallBack, ArgumentList, "Execute Procedure RegenAll()")
DB.ExecuteInBackGround(ContinueCallBack, ArgumentList, "Update OriginalTable Set [...] Where [...]")
其中所有语句都可能很慢(例如,会计科目再生)。ContinueCallBack
方法可以检查 UI 上的“取消”按钮是否被点击。
Private Sub CmdCancel_Click(sender As Object, e As EventArgs) Handles CmdCancel.Click
CancelClicked = True
DB.BreakCommand()
End Sub
Private Sub ContinueCallBackFunction(SqlResult As Database.SqlResult, Args As Object())
If CancelClicked Then
DB.BreakCommand(True)
DB.RollBack()
Exit Sub
End If
If SqlResult.HasError Then
[...]
End If
UpdateProgressBar()
End Sub
派生类还必须实现 Clone 方法,当您需要管理后台操作时,它非常有用。所有连接器一次只能执行一个操作,所以您不能请求后台操作,并且在后台操作期间调用另一个语句的执行。为了避免这种情况,您可以使用 Clone 方法。
Dim DB As DataBase = New SqlServerDatabase(ServerName, DBName, User, Password)
Dim DB1 = DB.Clone()
DB1.ExecuteInBackground([...])
DB.Execute([...])
这样就有两个不同的连接,每个实例一个。请求是并发的。如果您使用同一个连接,两个命令中的一个将抛出异常,即 SqlResult 将包含异常。
Clone 方法对于使用 DataReader 也很有用。默认情况下,查询会返回到数据集中,但您也可以检索 DataReader 中的数据。当一个语句很慢时(行数很多且未使用索引),并且您需要为每一行执行大量工作,这可能很有用。事实上,使用数据集执行的操作与使用 DataReader 相同,但返回数据是在检索完最后一行并填充了所有行的数据集后执行的。DataReader 允许在检索到第一行时返回控件,并且如果搜索第二行很慢,如果处理每一行都非常慢,您可以一边处理第一行,一边让数据库服务器搜索其他行。这样可以优化性能。如果其中一个或两个操作都很快,数据集允许一次性管理所有检索到的数据,例如使用 LinQ,或返回到第一行,或者再次访问非顺序行 - 如果您尝试第二次搜索第一行,DataReader 会再次执行查询。
DataReader 的另一个限制是,在 DataReader 关闭之前,您不能执行任何其他操作。为此,您可以使用 clone 方法同时执行更多操作。
Using DB As DataBase = New SqlServerDatabase(ServerName, DBName, User, Password)
Using Result As SqlResult = DB.Execute(CallType.SelectToReader, "Select [...]")
Using DB1 As DataBase = DB.Clone()
While Result.DataReader.Read()
DB1.Execute([...])
End While
End Using
End Using
End Using
还有一系列调试工具:如果解决方案处于调试模式,则 DebugMode
属性假定所有请求都将打印到调试窗口。还可以写入日志文件,其中您将在刷新后找到所有请求 - 刷新在每次发出关闭请求时进行,或者在 closingConnectionTimer
滴答后进行。虽然 DebugMode
不在所有连接之间共享,但您可以将其设置在构造函数中,或者为特定的 Database 实例设置 - DebugFileLocation
,它设置了日志记录的 StreamWriter,在所有实例之间共享,以避免冲突。
此外,Data2String
函数对于调试非常有用,并且被它使用:您可以查看 DataReader、DataTable 的内容,或者,如果您使用 DataView 或 DataTable 的 Select 方法过滤数据,它将检索任何可枚举类型的 DataRow 和 DataRowView 对象的 Select 结果。如果您启动单个行或行视图的 Data2String,它会(可选地)搜索该行的整个行集,以显示当前行索引以及表中其余部分。这样,如果您在循环扫描数据表的行,您可以看到扫描的进度。