SQL数据库编辑器






3.80/5 (9投票s)
2004年8月26日
2分钟阅读

114926

2150
一篇关于 SQL 数据库编辑器的文章。
引言
我写这个小程序是为了帮一个朋友的忙,主要是为了开始从 VB6 迁移到 .NET 的过程。 结果却是一次很好的学习经历。
背景
当您需要编辑 SQL Server 数据库中的数据,但手头没有企业管理器时,您会怎么做? 这就是我编写的最后一个应用程序遇到的问题。 有一天,一个朋友问我是否可以写一个类似的东西。 所以我做了,并且我认为其他程序员也可以使用它,所以我发布了二进制文件和源代码。
我发现使用 ADO.NET 比 ADO 轻松得多。 在 .NET 中做在 VB6 中无法完成的事情非常容易。 直到我尝试在没有主键的表上使用 CommandBuilder
类。 事实证明,CommandBuilder
类仅适用于定义了主键的表。 因此,我编写了三个函数来为没有主键的表构建 Insert
、Update
和 Delete
命令。 现在,我不是 SQL 大师,但到目前为止,我还没有遇到任何问题。 我必须指出,这些函数仅适用于表。 您必须将一个合法的 SQL 表的名称传递给该函数,该函数然后将返回 SQL 命令字符串,用于从/向该表插入、更新或删除项目。
使用代码
为了方便不想阅读应用程序中所有代码的人,这里有三个函数及其辅助函数
Private Sub BuildInsertCommand(ByRef dataAdapter As _
System.Data.SqlClient.SqlDataAdapter, _
ByVal tableName As String)
Dim t_dt As DataTable = _
GetTableColumnInfo(dataAdapter.SelectCommand.Connection, _
tableName)
Dim dv As New DataView(t_dt)
Dim cmd As SqlClient.SqlCommand
Dim dt As New DataTable
Dim dca() As DataColumn
Dim dc As DataColumn
Dim sFilterFieldnames() As String
Dim sParamNames() As String
Dim sSQL As String
Dim sTmp As String
Dim sSQLDataType As String
Dim lPrecision As Byte
Dim lSize As Integer
Dim lScale As Byte
'Get the schema for the table to be read.
dataAdapter.FillSchema(dt, SchemaType.Source)
ReDim sFilterFieldnames(dt.Columns.Count - 1)
ReDim sParamNames(dt.Columns.Count - 1)
For Each dc In dt.Columns
'Read & save the column names
sFilterFieldnames(dc.Ordinal) = dc.ColumnName
'Read the column names and generate parameter names.
sParamNames(dc.Ordinal) = "@" + _
RemoveIllegalChars(dc.ColumnName, m_RemoveChars)
Next
'Clean up
dc.Dispose()
dca = dt.PrimaryKey()
'Insert Command ***********************************
sSQL = "INSERT INTO [" + tableName + "] ("
' Fields
For i As Integer = sFilterFieldnames.GetLowerBound(0) _
To sFilterFieldnames.GetUpperBound(0)
sSQL = sSQL & "[" & sFilterFieldnames(i) & "]"
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + ", "
Else
sSQL = sSQL + ")"
End If
Next
' Parameters
sSQL = sSQL + " VALUES ("
For i As Integer = sParamNames.GetLowerBound(0) _
To sParamNames.GetUpperBound(0)
sSQL = sSQL + sParamNames(i)
If i <> sParamNames.GetUpperBound(0) Then
sSQL = sSQL + ", "
Else
sSQL = sSQL + "); "
End If
Next
' Do a SELECT again
sSQL = sSQL + "SELECT "
For i As Integer = sFilterFieldnames.GetLowerBound(0) To _
sFilterFieldnames.GetUpperBound(0)
sSQL = sSQL + "[" + sFilterFieldnames(i) + "]"
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + ", "
End If
Next
sSQL = sSQL + " FROM [" + tableName + "] WHERE ("
' WHERE Clause
If dca.GetLength(0) > 0 Then
'If the table contains a primary key, use that to find the record.
For i As Integer = dca.GetLowerBound(0) To dca.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & dca(i).ColumnName & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
sTmp = "[" + dca(i).ColumnName + "] LIKE @" + _
RemoveIllegalChars(dca(i).ColumnName, _
m_RemoveChars)
Else
sTmp = "[" + dca(i).ColumnName + "] = @" + _
RemoveIllegalChars(dca(i).ColumnName, _
m_RemoveChars)
End If
If dca.GetLength(0) > 1 Then
sSQL = sSQL + "(" + sTmp + ")"
Else
sSQL = sSQL + sTmp
End If
If i <> dca.GetUpperBound(0) Then sSQL = sSQL + " AND "
Next
Else
'otherwise use a combination of the fields.
For i As Integer = sFilterFieldnames.GetLowerBound(0) _
To sFilterFieldnames.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
sTmp = "[" + sFilterFieldnames(i) + "] LIKE " + sParamNames(i)
Else
sTmp = "[" + sFilterFieldnames(i) + "] = " + sParamNames(i)
End If
If sFilterFieldnames.GetLength(0) > 1 Then
sSQL = sSQL + "(" + sTmp + ")"
Else
sSQL = sSQL + sTmp
End If
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + " AND "
Next
End If
sSQL = sSQL + ")"
'Create a new command object
cmd = New SqlClient.SqlCommand(sSQL)
'Set it's connection from the Select cammand in the DataAdapter
cmd.Connection = dataAdapter.SelectCommand.Connection()
' Add Parameters
For i As Integer = sFilterFieldnames.GetLowerBound(0) _
To sFilterFieldnames.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
Try
lPrecision = CType(dv(0)("PRECISION").ToString, Byte)
Catch ex As Exception
lPrecision = 38
End Try
If lPrecision > 38 Then lPrecision = 38
lSize = CType(dv(0)("LENGTH").ToString, Integer)
lScale = CType(IIf(dv(0)("SCALE").ToString.Equals(""), 0, _
dv(0)("SCALE").ToString), Byte)
cmd.Parameters.Add(New SqlClient.SqlParameter(sParamNames(i), _
GetSQLType(sSQLDataType), _
lSize, _
ParameterDirection.Input, _
dt.Columns(sFilterFieldnames(i)).AllowDBNull, _
lPrecision, _
lScale, _
sFilterFieldnames(i), _
DataRowVersion.Current, _
Nothing))
Next
'Clean up
dv.Dispose()
t_dt.Dispose()
dt.Dispose()
'Set the command object
dataAdapter.InsertCommand = cmd
End Sub
Private Sub BuildUpdateCommand(ByRef dataAdapter As _
System.Data.SqlClient.SqlDataAdapter, _
ByVal tableName As String)
Dim t_dt As DataTable = _
GetTableColumnInfo(dataAdapter.SelectCommand.Connection, tableName)
Dim dv As New DataView(t_dt)
Dim cmd As SqlClient.SqlCommand
Dim dt As New DataTable
Dim dca() As DataColumn
Dim dc As DataColumn
Dim sFilterFieldnames() As String
Dim sParamNames() As String
Dim sOrigParamNames() As String
Dim sSQL As String
Dim sTmp As String
Dim sSQLDataType As String
Dim lPrecision As Byte
Dim lSize As Integer
Dim lScale As Byte
'Get the schema for the table to be read.
dataAdapter.FillSchema(dt, SchemaType.Source)
ReDim sFilterFieldnames(dt.Columns.Count - 1)
ReDim sParamNames(dt.Columns.Count - 1)
ReDim sOrigParamNames(dt.Columns.Count - 1)
For Each dc In dt.Columns
'Read & save the column names
sFilterFieldnames(dc.Ordinal) = dc.ColumnName
'Read the column names and generate parameter names.
sParamNames(dc.Ordinal) = "@" + _
RemoveIllegalChars(dc.ColumnName, m_RemoveChars)
'Read the column names and generate Original parameter names.
sOrigParamNames(dc.Ordinal) = "@Original_" + _
RemoveIllegalChars(dc.ColumnName, m_RemoveChars)
Next
'Clean up
dc.Dispose()
dca = dt.PrimaryKey()
'Update Command ***********************************
sSQL = "UPDATE [" & tableName & "] SET "
' Fields
For i As Integer = sFilterFieldnames.GetLowerBound(0) _
To sFilterFieldnames.GetUpperBound(0)
sSQL = sSQL + "[" + sFilterFieldnames(i) + "] = " + sParamNames(i)
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + ", "
Else
sSQL = sSQL + " "
End If
Next
' Parameters
sSQL = sSQL + " WHERE "
If dca.GetLength(0) > 0 Then
'If the table contains a primary key, use that to find the record.
For i As Integer = dca.GetLowerBound(0) To dca.GetUpperBound(0)
sTmp = "[" + dca(i).ColumnName + "]"
dv.RowFilter = "COLUMN_NAME = '" & dca(i).ColumnName & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
sSQL = sSQL + "(" + sTmp + " LIKE @Original_" + _
RemoveIllegalChars(dca(i).ColumnName, _
m_RemoveChars)
Else
sSQL = sSQL + "(" + sTmp + " = @Original_" + _
RemoveIllegalChars(dca(i).ColumnName, m_RemoveChars)
End If
If i <> dca.GetUpperBound(0) Then
sSQL = sSQL + " AND "
Else
sSQL = sSQL + "; "
End If
Next
Else
'otherwise use a combination of the fields.
For i As Integer = sFilterFieldnames.GetLowerBound(0) To _
sFilterFieldnames.GetUpperBound(0)
sTmp = "[" + sFilterFieldnames(i) + "]"
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
If dt.Columns(sFilterFieldnames(i)).AllowDBNull Then
sSQL = sSQL + "((" + sTmp + " LIKE " + _
sOrigParamNames(i) + ") OR _
(" + sTmp + " IS NULL AND " + _
sOrigParamNames(i) + " IS NULL))"
Else
sSQL = sSQL + "(" + sTmp + " LIKE " + _
sOrigParamNames(i) + ")"
End If
Else
If dt.Columns(sFilterFieldnames(i)).AllowDBNull Then
sSQL = sSQL + "((" + sTmp + " = " + _
sOrigParamNames(i) + ") OR _
(" + sTmp + " IS NULL AND " + _
sOrigParamNames(i) + " IS NULL))"
Else
sSQL = sSQL + "(" + sTmp + " = " + sOrigParamNames(i) + ")"
End If
End If
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + " AND "
Else
sSQL = sSQL + "; "
End If
Next
End If
' Do a SELECT again
sSQL = sSQL + "SELECT "
For i As Integer = sFilterFieldnames.GetLowerBound(0) To _
sFilterFieldnames.GetUpperBound(0)
sSQL = sSQL + "[" + sFilterFieldnames(i) + "]"
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + ", "
End If
Next
sSQL = sSQL + " FROM [" + tableName + "] WHERE ("
' WHERE Clause
If dca.GetLength(0) > 0 Then
'If the table contains a primary key, use that to find the record.
For i As Integer = dca.GetLowerBound(0) To dca.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & dca(i).ColumnName & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
sTmp = "[" + dca(i).ColumnName + "] LIKE @" + _
RemoveIllegalChars(dca(i).ColumnName, _
m_RemoveChars)
Else
sTmp = "[" + dca(i).ColumnName + "] = @" + _
RemoveIllegalChars(dca(i).ColumnName, m_RemoveChars)
End If
If dca.GetLength(0) > 1 Then
sSQL = sSQL + "(" + sTmp + ")"
Else
sSQL = sSQL + sTmp
End If
If i <> dca.GetUpperBound(0) Then sSQL = sSQL + " AND "
Next
Else
'otherwise use a combination of the fields.
For i As Integer = sFilterFieldnames.GetLowerBound(0) To _
sFilterFieldnames.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
sTmp = "[" + sFilterFieldnames(i) + "] LIKE " + sParamNames(i)
Else
sTmp = "[" + sFilterFieldnames(i) + "] = " + sParamNames(i)
End If
If sFilterFieldnames.GetLength(0) > 1 Then
sSQL = sSQL + "(" + sTmp + ")"
Else
sSQL = sSQL + sTmp
End If
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + " AND "
Next
End If
sSQL = sSQL + ")"
'Create a new command object
cmd = New SqlClient.SqlCommand(sSQL)
'Set it's connection from the Select cammand in the DataAdapter
cmd.Connection = dataAdapter.SelectCommand.Connection()
' Add Parameters
For i As Integer = sFilterFieldnames.GetLowerBound(0) To _
sFilterFieldnames.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
Try
lPrecision = CType(dv(0)("PRECISION").ToString, Byte)
Catch ex As Exception
lPrecision = 38
End Try
If lPrecision > 38 Then lPrecision = 38
lSize = CType(dv(0)("LENGTH").ToString, Integer)
lScale = CType(IIf(dv(0)("SCALE").ToString.Equals(""), _
0, dv(0)("SCALE").ToString), Byte)
cmd.Parameters.Add(New SqlClient.SqlParameter(sParamNames(i), _
GetSQLType(sSQLDataType), _
lSize, _
ParameterDirection.Input, _
dt.Columns(sFilterFieldnames(i)).AllowDBNull, _
lPrecision, _
lScale, _
sFilterFieldnames(i), _
DataRowVersion.Current, _
Nothing))
cmd.Parameters.Add(New SqlClient.SqlParameter(sOrigParamNames(i), _
GetSQLType(sSQLDataType), _
lSize, _
ParameterDirection.Input, _
dt.Columns(sFilterFieldnames(i)).AllowDBNull, _
lPrecision, _
lScale, _
sFilterFieldnames(i), _
DataRowVersion.Original, _
Nothing))
Next
'Clean up
dv.Dispose()
t_dt.Dispose()
dt.Dispose()
'Set the command object
dataAdapter.UpdateCommand = cmd
End Sub
Private Sub BuildDeleteCommand(ByRef dataAdapter _
As System.Data.SqlClient.SqlDataAdapter, _
ByVal tableName As String)
Dim t_dt As DataTable = _
GetTableColumnInfo(dataAdapter.SelectCommand.Connection, tableName)
Dim dv As New DataView(t_dt)
Dim cmd As SqlClient.SqlCommand
Dim dt As New DataTable
Dim dca() As DataColumn
Dim dc As DataColumn
Dim sFilterFieldnames() As String
Dim sOrigParamNames() As String
Dim sSQL As String
Dim sTmp As String
Dim sSQLDataType As String
Dim lPrecision As Byte
Dim lSize As Integer
Dim lScale As Byte
'Get the schema for the table to be read.
dataAdapter.FillSchema(dt, SchemaType.Source)
ReDim sFilterFieldnames(dt.Columns.Count - 1)
ReDim sOrigParamNames(dt.Columns.Count - 1)
For Each dc In dt.Columns
'Read & save the column names
sFilterFieldnames(dc.Ordinal) = dc.ColumnName
'Read the column names and generate Original parameter names.
sOrigParamNames(dc.Ordinal) = "@Original_" + _
RemoveIllegalChars(dc.ColumnName, m_RemoveChars)
Next
'Clean up
dc.Dispose()
dca = dt.PrimaryKey()
'Delete Command ***********************************
sSQL = "DELETE FROM [" & tableName & "] WHERE "
' Parameters
If dca.GetLength(0) > 0 Then
'If the table contains a primary key, use that to find the record.
For i As Integer = dca.GetLowerBound(0) To dca.GetUpperBound(0)
sTmp = "[" + dca(i).ColumnName + "]"
dv.RowFilter = "COLUMN_NAME = '" & dca(i).ColumnName & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
sSQL = sSQL + "(" + sTmp + " LIKE @Original_" + _
RemoveIllegalChars(dca(i).ColumnName, _
m_RemoveChars) + ")"
Else
sSQL = sSQL + "(" + sTmp + " = @Original_" + _
RemoveIllegalChars(dca(i).ColumnName, _
m_RemoveChars) + ")"
End If
If i <> dca.GetUpperBound(0) Then
sSQL = sSQL + " AND "
End If
Next
Else
'otherwise use a combination of the fields.
For i As Integer = sFilterFieldnames.GetLowerBound(0) _
To sFilterFieldnames.GetUpperBound(0)
sTmp = "[" + sFilterFieldnames(i) + "]"
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
If sSQLDataType.Equals("text") Or _
sSQLDataType.Equals("ntext") Or _
sSQLDataType.Equals("image") Then
If dt.Columns(sFilterFieldnames(i)).AllowDBNull Then
sSQL = sSQL + "((" + sTmp + " LIKE " + _
sOrigParamNames(i) + ") OR _
(" + sTmp + " IS NULL AND " + _
sOrigParamNames(i) + " IS NULL))"
Else
sSQL = sSQL + "(" + sTmp + " LIKE " + sOrigParamNames(i) + ")"
End If
Else
If dt.Columns(sFilterFieldnames(i)).AllowDBNull Then
sSQL = sSQL + "((" + sTmp + " = " + _
sOrigParamNames(i) + ") OR _
(" + sTmp + " IS NULL AND " + _
sOrigParamNames(i) + " IS NULL))"
Else
sSQL = sSQL + "(" + sTmp + " = " + sOrigParamNames(i) + ")"
End If
End If
If i <> sFilterFieldnames.GetUpperBound(0) Then
sSQL = sSQL + " AND "
End If
Next
End If
'Create a new command object
cmd = New SqlClient.SqlCommand(sSQL)
'Set it's connection from the Select cammand in the DataAdapter
cmd.Connection = dataAdapter.SelectCommand.Connection()
' Add Parameters
For i As Integer = sFilterFieldnames.GetLowerBound(0) _
To sFilterFieldnames.GetUpperBound(0)
dv.RowFilter = "COLUMN_NAME = '" & sFilterFieldnames(i) & "'"
sSQLDataType = dv(0)("TYPE_NAME").ToString()
Try
lPrecision = CType(dv(0)("PRECISION").ToString, Byte)
Catch ex As Exception
lPrecision = 38
End Try
If lPrecision > 38 Then lPrecision = 38
lSize = CType(dv(0)("LENGTH").ToString, Integer)
lScale = CType(IIf(dv(0)("SCALE").ToString.Equals(""), _
0, dv(0)("SCALE").ToString), Byte)
cmd.Parameters.Add(New SqlClient.SqlParameter(sOrigParamNames(i), _
GetSQLType(sSQLDataType), _
lSize, _
ParameterDirection.Input, _
dt.Columns(sFilterFieldnames(i)).AllowDBNull, _
lPrecision, _
lScale, _
sFilterFieldnames(i), _
DataRowVersion.Original, _
Nothing))
Next
'Clean up
dv.Dispose()
t_dt.Dispose()
dt.Dispose()
'Set the command object
dataAdapter.DeleteCommand = cmd
End Sub
Private Function GetSQLType(ByVal sSQLTypeName _
As String) As System.Data.SqlDbType
Select Case sSQLTypeName.ToLower()
Case "bigint"
GetSQLType = SqlDbType.BigInt
Case "binary"
GetSQLType = SqlDbType.Binary
Case "bit"
GetSQLType = SqlDbType.Bit
Case "char"
GetSQLType = SqlDbType.Char
Case "datetime"
GetSQLType = SqlDbType.DateTime
Case "decimal"
GetSQLType = SqlDbType.Decimal
Case "float"
GetSQLType = SqlDbType.Float
Case "image"
GetSQLType = SqlDbType.Image
Case "int"
GetSQLType = SqlDbType.Int
Case "money"
GetSQLType = SqlDbType.Money
Case "nchar"
GetSQLType = SqlDbType.NChar
Case "ntext"
GetSQLType = SqlDbType.NText
Case "nvarchar"
GetSQLType = SqlDbType.NVarChar
Case "real"
GetSQLType = SqlDbType.Real
Case "smalldatetime"
GetSQLType = SqlDbType.SmallDateTime
Case "smallint"
GetSQLType = SqlDbType.SmallInt
Case "smallmoney"
GetSQLType = SqlDbType.SmallMoney
Case "sql_variant"
GetSQLType = SqlDbType.Variant
Case "text"
GetSQLType = SqlDbType.Text
Case "timestamp"
GetSQLType = SqlDbType.Timestamp
Case "tinyint"
GetSQLType = SqlDbType.TinyInt
Case "uniqueidentifier"
GetSQLType = SqlDbType.UniqueIdentifier
Case "varbinary"
GetSQLType = SqlDbType.VarBinary
Case "varchar"
GetSQLType = SqlDbType.VarChar
Case Else
GetSQLType = SqlDbType.Variant
End Select
End Function
Private Function GetTableColumnInfo(ByRef connection _
As System.Data.SqlClient.SqlConnection, _
ByVal tableName As String) _
As System.Data.DataTable
Dim t_cmd As New SqlClient.SqlCommand("sp_columns")
t_cmd.CommandType = CommandType.StoredProcedure
t_cmd.Connection = connection
t_cmd.Parameters.Add("@table_name", _
SqlDbType.NVarChar, 384).Value = tableName
Dim t_da As New SqlClient.SqlDataAdapter(t_cmd)
Dim t_dt As New DataTable
Try
t_da.Fill(t_dt)
GetTableColumnInfo = t_dt
Catch ex As Exception
GetTableColumnInfo = Nothing
End Try
t_cmd.Dispose()
t_da.Dispose()
End Function
Private Function RemoveIllegalChars(ByVal _
value As String, ByVal chars As String) As String
Dim i As Integer
For i = 0 To chars.Length - 1
value = value.Replace(chars.Substring(i, 1), "")
Next
Return value
End Function
关注点
首先,我要感谢 Tim Dawson、Jeff Atwood 和 Rockford Lhotka 等人免费使用他们的代码和控件。 该应用程序的“关于”框中特别提到了这一点。
请注意:我没有在压缩包中包含 Tim Dawson 的 SandBar 或 SandDock 控件,尽管该应用程序需要这些控件才能运行。 请从他的 站点 下载这些控件。
说够了! 我希望有人觉得这个应用程序有用。 如果有人可以扩展它并重新发布它,那就更好了。
历史
版本 1.3.1687.36192: 已发布。