更新行集合
一个非常基本的数据库操作就是更新(或将数据写入)数据存储区。 OLE DB 中的更新机制非常简单:使用者应用程序设置绑定的数据成员的值,并将这些值写入行集合;然后使用者请求提供程序更新数据存储区。
使用者可以对行集合数据执行以下几种更新操作:在行内设置列值、插入行和删除行。 为执行这些操作,OLE DB 模板类 CRowset 实现了 IRowsetChange 接口,并重写下列接口方法:
SetData 在行集合的某一行中更改列值;它等效于 SQL UPDATE 命令。
Insert 向行集合中插入一行;它等效于 SQL INSERT 命令。
Delete 从行集合中删除行;它等效于 SQL DELETE 命令。
支持更新操作
当用“ATL OLE DB 使用者向导”创建使用者时,可以通过选择三个复选框 Change、Insert 和 Delete 中的一个或多个来支持更新操作。 选择这些复选框后,向导将适当地修改代码以支持所选的更改类型。 但如果不使用向导,则需要将以下行集合属性设置为 VARIANT_TRUE 来支持更新:
DBPROPVAL_UP_CHANGE 使您可以在某一行中更改数据值。
DBPROPVAL_UP_INSERT 使您可以插入一行。
DBPROPVAL_UP_DELETE 使您可以删除一行。
按如下所示设置这些属性:
CDBPropSet ps(DBPROPSET_ROWSET);
ps.AddProperty(DBPROP_IRowsetChange, true)
ps.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE)
如果一个或多个列是不可写的,则更改、插入或删除操作可能会失败。 修改游标映射以更正此问题。
在行中设置数据
CRowset::SetData 在当前行的一个或多个列中设置数据值。 以下代码设置绑定到 Products 表的“Name”和“Units in Stock”列的数据成员的值,然后调用 SetData 将这些值写入行集合的第 100 行:
// Instantiate a rowset based on the user record class
CTable<CAccessor<CProductAccessor> > product;
CSession session;
// Open the rowset and move to the 100th row
product.Open(session, "Product", &ps, 1); // ps is the property set
product.MoveToBookmark(&bookmark, 0); // Assume that bookmark is set to 100th row
// Change the values of columns "Name" and "Units in Stock" in the current row of the Product table
_tcscpy_s( product.m_ProductName, product.m_sizeOfProductName,
_T( "Candle" ) );
product.m_UnitsInStock = 10000;
// Set the data
HRESULT hr = product.SetData( );
向行集合中插入行
CRowset::Insert 使用访问器中的数据创建并初始化新行。 Insert 在当前行的后面创建一个全新的行;需要指定是将当前行递增到下一行还是保持当前行不变。 可以通过设置 bGetRow 参数完成此操作:
HRESULT Insert(int nAccessor = 0, bool bGetRow = false)
false(默认值)指定当前行递增到下一行(在这种情况下它将指向被插入的行)。
true 指定当前行保持原来位置。
以下代码设置绑定到 Products 表的列的数据成员的值,然后调用 Insert 将一个具有这些值的新行插入到该行集合的第 100 行后面。 建议设置所有列值以避免在新行中出现未定义的数据:
// Instantiate a rowset based on the user record class
CTable<CAccessor<CProductAccessor> > product;
CSession session;
// Open the rowset and move to the 100th row
product.Open(session, "Product", &ps, 1); // ps is the property set
product.MoveToBookmark(&bookmark, 0); // Assume that bookmark is set to 100th row
// Set the column values for a row of the Product table, then insert the row
product.m_ProductID = 101;
_tcscpy_s( product.m_ProductName, product.m_sizeOfProductName,
_T( "Candle" ) );
product.m_SupplierID = 27857;
product.m_CategoryID = 372;
_tcscpy_s( product.m_QuantityPerUnit, product.m_sizeOfQuantityPerUnit,
_T( "Pack of 10" ) );
product.m_UnitPrice = 20;
product.m_UnitsInStock = 10000;
product.m_UnitsOnOrder = 5201;
product.m_ReorderLevel = 5000;
product.m_Discontinued = false;
// You must also initialize the status and length fields before setting/inserting data
// Set the column status values
m_dwProductIDStatus = DBSTATUS_S_OK;
m_dwProductNameStatus = DBSTATUS_S_OK;
m_dwSupplierIDStatus = DBSTATUS_S_OK;
m_dwCategoryIDStatus = DBSTATUS_S_OK;
m_dwQuantityPerUnitStatus = DBSTATUS_S_OK;
m_dwUnitPriceStatus = DBSTATUS_S_OK;
m_dwUnitsInStockStatus = DBSTATUS_S_OK;
m_dwUnitsOnOrderStatus = DBSTATUS_S_OK;
m_dwReorderLevelStatus = DBSTATUS_S_OK;
m_dwDiscontinuedStatus = DBSTATUS_S_OK;
// Set the column length value for column data members that are not fixed-length types.
// The value should be the length of the string that you are setting.
m_dwProductNameLength = 6; // "Candle" has 6 characters
m_dwQuantityPerUnitLength = 10; // "Pack of 10" has 10 characters
// Insert the data
HRESULT hr = product.Insert( );
有关更详细的示例,请参见 CRowset::Insert。
有关设置状态和长度数据成员的更多信息,请参见向导生成的访问器中的字段状态数据成员。
从行集合中删除行
CRowset::Delete 从行集合中删除当前行。 以下代码调用 Delete 以移除行集合的第 100 行:
// Instantiate a rowset based on the user record class
CTable<CAccessor<CProductAccessor> > product;
CSession session;
// Open the rowset and move to the 100th row
product.Open(session, "Product", &ps, 1); // ps is the property set
product.MoveToBookmark(&bookmark, 0); // Assume that bookmark is set to 100th row
// Delete the row
HRESULT hr = product.Delete( );
立即更新和推迟更新
除非另外指定,否则对 SetData、Insert 和 Delete 方法的调用将立即更新数据存储区。 但您可以推迟更新,以便使用者将所有更改存储在本地缓存中,然后在调用以下更新方法之一时将这些更改传输到数据存储区:
CRowset::Update 传输自从对行集合进行最后一次获取或 Update 调用以来对当前行所做的所有挂起的更改。
CRowset::UpdateAll 传输自从对行集合进行最后一次获取或 Update 调用以来对所有行所做的所有挂起的更改。
注意,更新方法使用的“更新”具有特定含义:“在命令上进行更改”,不应与 SQL UPDATE 命令混淆(SetData 等效于 SQL UPDATE 命令)。
推迟更新在某些情况下很有用,如发生一系列银行事务这样的情况下;如果单个事务被取消,则可以撤消此更改,因为在提交最后一次更改之前不会发送这一系列更改。 提供程序还可以将更改捆绑到单个网络调用,这样效率会更高。
若要支持推迟更新,除了要设置“支持更新操作”中描述的属性外,还必须设置 DBPROP_IRowsetChange 属性:
pPropSet->AddProperty(DBPROP_IRowsetUpdate, true);
当调用 Update 或 UpdateAll 时,这些方法将更改从本地缓存传输到数据存储区,然后擦除本地缓存。 Update 仅传输当前行的更改,因此应用程序跟踪要更新的行以及更新时间就显得很重要。 下面的示例显示如何更新两个连续的行:
// Instantiate a rowset based on the user record class
CTable<CAccessor<CProductAccessor> > product;
CSession session;
// Open the rowset and move to the 100th row
product.Open(session, "Product", &ps, 1); // ps is the property set
product.MoveToBookmark(&bookmark, 0); // Assume that bookmark is set to 100th row
// Change the values of columns "Name" and "Units in Stock" in the 100th row of the Product table
_tcscpy_s( product.m_ProductName, product.m_sizeOfProductName,
_T( "Wick" ) );
product.m_UnitsInStock = 10000;
HRESULT hr = product.SetData( ); // No changes made to row 100 yet
product.Update(); // Update row 100 now
// Change the values of columns "Name" and "Units in Stock" in the 101st row of the Product table
product.MoveNext( );
_tcscpy_s( product.m_ProductName, product.m_sizeOfProductName
_T( "Wax" ) );
product.m_UnitsInStock = 500;
HRESULT hr = product.SetData( ); // No changes made to row 101 yet
product.Update(); // Update row 101 now
为了确保挂起的更改被传输,应在移动到另一行之前调用 Update。 但如果这样做单调乏味或者效率很低(例如,当应用程序需要更新成百上千行时),则可以使用 UpdateAll 一次更新所有行。
例如,如果上面的代码中缺少第一个 Update 调用,则第 100 行将保持不变,而第 101 行将会更改。 此时,应用程序将不得不调用 UpdateAll 或移动回第 100 行并调用 Update 以更新该行。
最后,推迟更改的一个主要原因是可以将这些更改撤消。 调用 CRowset::Undo 可将本地更改缓存的状态回滚到进行任何挂起的更改之前数据存储区的状态。 值得注意的是,Undo 不会只回滚到本地缓存的前一状态(即最后一次更改之前的状态),而是清除该行的本地缓存。 而且,Undo 仅影响当前行。