如何在 ADO.NET 应用程序中刷新当前行






3.11/5 (3投票s)
2007年2月27日
4分钟阅读

55160

645
使用 ODP.NET 数据提供程序在 ADO.NET 应用程序中刷新当前行

引言
如今,大多数应用程序都使用不同的数据库。对于 .NET 程序,访问数据库最流行的方式是使用 ADO.NET 组件。众所周知,ADO.NET 使用断开连接模型和名为 DataTable
的组件,该组件没有“当前数据行”的概念。但开发人员经常需要刷新当前数据行,因为该行可能已被另一个应用程序或服务器端逻辑更改。我认为下面的代码是 Oracle 数据库对此问题的解决方案,但同样适用于任何其他 RDBMS。
必备组件
所有代码示例都适用于 Microsoft Visual Studio 2005。我使用的数据库是 Oracle 10i 和 Oracle Data Provider (ODP.NET) 版本 10.2.0.2.20。您可以在 Oracle 网站上下载:http://www.oracle.com/technology/software/tech/windows/odpnet/index.html。
现实
ADO.NET 开发人员给了我们一个非常“有用”的事件,名为 RowUpdated
。如果您使用 DataAdapter
组件更新了一行,您就会知道您已经更新了。这很好,但如果该行已被另一个用户或服务器端逻辑更新,您将一无所知。在我之前的文章中,我考虑了 ADO.NET 并发模型的一些方面。原则上,本文是前一篇文章的逻辑延续,因为两篇文章有一个共同的目标:解决 ADO.NET 模型限制。
解决问题的方法
我们应该解决以下问题:
- 从
DataTable
获取当前行 - 从 Oracle 数据库获取与当前行对应的记录
- 将数据从记录放入
DataTable
(刷新当前行)
众所周知,ADO.NET 的 dataset 没有游标的概念,因此它也没有“当前行”的概念。但是,如果我们使用的应用程序有一个在窗体上的 DataGridView
组件,这个问题就很容易解决。我们可以简单地使用名为“BindingContext
”的属性,并从 DataGridView
获取当前行位置。这只需要三行代码。
public partial class Form1 : Form
{
..........
private BindingManagerBase bindingManagerBase;
public Form1()
{
InitializeComponent();
..........
bindingManagerBase =
dataGridView1.BindingContext[dataSet1.Tables["Colors"]];
..........
在应用程序代码的某个地方,我们可以获取当前行:
DataRow cur_row = dataSet1.Tables["Colors"].DefaultView
[bindingManagerBase.Position].Row;
所以,“1”点很简单。
要解决“2”和“3”点,我们首先需要决定如何标识当前行以便从数据库中获取它。显然,我们应该使用某个键。大多数数据库表都有主键,因此我们可以使用它们来标识我们的行。但为了使我们的解决方案更通用,我们可以使用字段名“ROWID
”。 Oracle 数据库的每个表和视图都有这个字段,但如果您需要在另一个 RDBMS 中刷新当前行,您应该使用其他内容。既然我们决定使用 ROWID
作为标识行的键,请不要忘记在您的“SELECT
”语句中包含 ROWID。
然后,我们可以编写一个名为“RefreshRow
”的类,它具有以下公共方法和属性:
public RefreshRow(OracleConnection conn) //constructor
public void Dispose() //destructor
public void Refresh(DataRow row) //refreshing row
public String SQL
// sql expression to extract row from database,
//it should has rowid parameter obligatory,
//for example "select t.*,
//t.rowid from colors t where t.rowid=:snotra__rowid"
Refresh
方法的工作方式非常简单:它从数据库提取一行并将数据放入 DataRow
。
public void Refresh(DataRow row)
{
OpenDMLQuery();
oracleDMLCommand.CommandText = sql;
oracleDMLCommand.Parameters.Add(":snotra__rowid", OracleDbType.Varchar2,
RowId(row), ParameterDirection.Input);
OracleDataReader odr = null;
try
{
odr = oracleDMLCommand.ExecuteReader();
if (odr.Read())
{
for (int i = 0; i < odr.FieldCount; i++)
{
DataColumn col = row.Table.Columns[odr.GetName(i)];
SetColumnValue(col, i, odr, row);
}
}
}
catch (OracleException oe)
{
System.Windows.Forms.MessageBox.Show(oe.Message, "Error",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.Message, "Error",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
}
finally
{
if (odr != null)
{
odr.Close();
odr.Dispose();
}
}
}
私有方法 SetColumnValue
根据列的数据类型将数据放入行。
private void SetColumnValue(DataColumn col, int index, OracleDataReader odr,
DataRow row)
{
if (odr.GetValue(index) == DBNull.Value)
{
row[col] = DBNull.Value;
return;
}
try
{
row.Table.BeginLoadData();
switch (Type.GetTypeCode(/*col.DataType*/odr.GetFieldType(index)))
{
case TypeCode.Int16:
row[col] = odr.GetInt16(index);
break;
case TypeCode.Int32:
row[col] = odr.GetInt32(index);
break;
case TypeCode.Int64:
row[col] = odr.GetInt64(index);
break;
case TypeCode.Decimal:
row[col] = odr.GetDecimal(index);
break;
case TypeCode.Single:
row[col] = odr.GetFloat(index);
break;
case TypeCode.Double:
row[col] = odr.GetDouble(index);
break;
case TypeCode.Byte:
row[col] = odr.GetByte(index);
break;
case TypeCode.Boolean:
row[col] = odr.GetBoolean(index);
break;
case TypeCode.String:
row[col] = odr.GetString(index);
break;
case TypeCode.DateTime:
row[col] = odr.GetDateTime(index);
break;
//add other types if you like
default:
row[col] = odr.GetValue(index);
break;
}
}
catch (Exception)
{
throw new Exception("column: " + col.ColumnName + " cast is not
valid");
}
finally
{
row.Table.EndLoadData();
}
}
根据这一行:
"switch (Type.GetTypeCode(/*col.DataType*/odr.GetFieldType(index)))"
此方法从 oracle DataReader
获取类型。如果您手动设置列的类型,可以取消注释“col.DataType
”行。
构建应用程序
在我们的实验中,我们将使用一个简单的 .NET 应用程序,该应用程序在 Form
上包含 DataGridView
组件,DataSet
和一个名为“RefreshCurrentRow
”的 Button
。我们的程序应该访问 Oracle 数据库,ODP.NET 非常适合。
在我们的示例中,我们将使用一个名为“COLORS
”的简单表。
create table COLORS
(
COLOR_ID NUMBER not null,
COLOR_NAME varchar2(100) not null,
RED number not null,
GREEN number not null,
BLUE number not null,
CONSTRAINT PK_COLORS PRIMARY KEY (COLOR_ID)
)
--fill table-------------
insert into colors(color_id, color_name, red, green, blue)
values(1, 'black', 0, 0, 0);
insert into colors(color_id, color_name, red, green, blue)
values(2, 'white', 254, 254, 254);
insert into colors(color_id, color_name, red, green, blue)
values(3, 'red', 254, 0, 0);
insert into colors(color_id, color_name, red, green, blue)
values(4, 'green', 0, 254, 0);
insert into colors(color_id, color_name, red, green, blue)
values(5, 'blue', 0, 0, 254);
insert into colors(color_id, color_name, red, green, blue)
values(6, 'yellow', 0, 254, 254);
commit;
下面是如何使用 RefreshRow
类的示例:
private void button1_Click(object sender, EventArgs e)
{
if (bindingManagerBase != null)
{
DataRow cur_row =
dataSet1.Tables["Colors"].DefaultView
[bindingManagerBase.Position].Row;
if (cur_row != null)
{
RefreshRow refreshRow = new RefreshRow(connection);
refreshRow.SQL = "select t.*,
t.rowid from colors t where
t.rowid=:snotra__rowid";
refreshRow.Refresh(cur_row);
refreshRow.Dispose();
}
}
}
它需要一个 OracleConnection
对象、一个包含 ROWID
列(必需)的 DataRow
对象和一个具有以下结构的 SQL 语句:
"t.rowid=:snotra__rowid"
就是这样。现在我们可以进行测试了。
测试
首先运行测试应用程序。然后启动 sqlplus 并执行命令:
update colors set red=1, green=1, blue=1 where color_id=1;
commit;
然后选择 DataGridView
中的第一行(颜色名称为 black)并按“RefreshCurrentRow
”按钮。RED
、GREEN
和 BLUE
列的值应该会改变。所以……太棒了,它奏效了!
如果您更喜欢其他 RDBMS 或其他数据提供程序,您应该使用适当的数据提供程序和数据类型重写 RefreshRow
类。
关注点
我从 Delphi 转到 .NET,并且对断开连接的 ADO.NET 组件提供的可能性感到有些沮丧。结果,我决定编写自己的解决方案,该解决方案允许我使用我需要的并发模型和其他一些附加功能,例如单记录刷新、部分数据选择等。