|
1 | 1 | ---
|
2 | 2 | title: "Creating an Updatable Provider | Microsoft Docs"
|
3 | 3 | ms.custom: ""
|
4 |
| -ms.date: "11/04/2016" |
| 4 | +ms.date: "08/16/2018" |
5 | 5 | ms.technology: ["cpp-data"]
|
6 | 6 | ms.topic: "reference"
|
7 | 7 | dev_langs: ["C++"]
|
@@ -134,3 +134,298 @@ Visual C++ supports updatable providers or providers that can update (write to)
|
134 | 134 | ## <a name="vchowwritingtothedatasource"></a> Writing to the Data Source
|
135 | 135 | To read from the data source, call the `Execute` function. To write to the data source, call the `FlushData` function. (In a general sense, flush means to save modifications you make to a table or index to disk.)
|
136 | 136 |
|
| 137 | +```cpp |
| 138 | + |
| 139 | +FlushData(HROW, HACCESSOR); |
| 140 | + |
| 141 | +``` |
| 142 | +
|
| 143 | +The row handle (HROW) and accessor handle (HACCESSOR) arguments allow you to specify the region to write. Typically, you write a single data field at a time. |
| 144 | +
|
| 145 | +The `FlushData` method writes data in the format in which it was originally stored. If you do not override this function, your provider will function correctly but changes will not be flushed to the data store. |
| 146 | +
|
| 147 | +### When to Flush |
| 148 | +The provider templates call FlushData whenever data needs to be written to the data store; this usually (but not always) occurs as a result of calls to the following functions: |
| 149 | +
|
| 150 | +- `IRowsetChange::DeleteRows` |
| 151 | +
|
| 152 | +- `IRowsetChange::SetData` |
| 153 | +
|
| 154 | +- `IRowsetChange::InsertRows` (if there is new data to insert in the row) |
| 155 | +
|
| 156 | +- `IRowsetUpdate::Update` |
| 157 | +
|
| 158 | +### How It Works |
| 159 | +
|
| 160 | +The consumer makes a call that requires a flush (such as Update) and this call is passed to the provider, which always does the following: |
| 161 | +
|
| 162 | +- Calls `SetDBStatus` whenever you have a status value bound. |
| 163 | +
|
| 164 | +- Checks column flags. |
| 165 | +
|
| 166 | +- Calls `IsUpdateAllowed`. |
| 167 | +
|
| 168 | +These three steps help provide security. Then the provider calls `FlushData`. |
| 169 | +
|
| 170 | +### How to Implement FlushData |
| 171 | +
|
| 172 | +To implement `FlushData`, you need to take into account several issues: |
| 173 | +
|
| 174 | +Making sure that the data store can handle changes. |
| 175 | +
|
| 176 | +Handling NULL values. |
| 177 | +
|
| 178 | +### Handling default values. |
| 179 | +
|
| 180 | +To implement your own FlushData method, you need to: |
| 181 | +
|
| 182 | +- Go to your rowset class. |
| 183 | +
|
| 184 | +- In the rowset class put the declaration of: |
| 185 | +
|
| 186 | + ```cpp |
| 187 | + HRESULT FlushData(HROW, HACCESSOR) |
| 188 | + { |
| 189 | + // Insert your implementation here and return an HRESULT. |
| 190 | + } |
| 191 | + ``` |
| 192 | + |
| 193 | +- Provide an implementation of `FlushData`. |
| 194 | + |
| 195 | +A good implementation of FlushData stores only the rows and columns that are actually updated. You can use the HROW and HACCESSOR parameters to determine the current row and column being stored for optimization. |
| 196 | + |
| 197 | +Typically, the biggest challenge is working with your own native data store. If possible, try to: |
| 198 | + |
| 199 | +- Keep the method of writing to your data store as simple as possible. |
| 200 | + |
| 201 | +- Handle NULL values (optional but advised). |
| 202 | + |
| 203 | +- Handle default values (optional but advised). |
| 204 | + |
| 205 | +The best thing to do is to have actual specified values in your data store for NULL and default values. It is best if you can extrapolate this data. If not, you are advised not to allow NULL and default values. |
| 206 | + |
| 207 | +The following example shows how `FlushData` is implemented in the RUpdateRowset class in the UpdatePV sample (see Rowset.h in the sample code): |
| 208 | + |
| 209 | +```cpp |
| 210 | +/////////////////////////////////////////////////////////////////////////// |
| 211 | +// class RUpdateRowset (in rowset.h) |
| 212 | +... |
| 213 | +HRESULT FlushData(HROW, HACCESSOR) |
| 214 | +{ |
| 215 | + ATLTRACE2(atlTraceDBProvider, 0, "RUpdateRowset::FlushData\n"); |
| 216 | + |
| 217 | + USES_CONVERSION; |
| 218 | + enum { |
| 219 | + sizeOfString = 256, |
| 220 | + sizeOfFileName = MAX_PATH |
| 221 | + }; |
| 222 | + FILE* pFile = NULL; |
| 223 | + TCHAR szString[sizeOfString]; |
| 224 | + TCHAR szFile[sizeOfFileName]; |
| 225 | + errcode err = 0; |
| 226 | + |
| 227 | + ObjectLock lock(this); |
| 228 | + |
| 229 | + // From a filename, passed in as a command text, |
| 230 | + // scan the file placing data in the data array. |
| 231 | + if (m_strCommandText == (BSTR)NULL) |
| 232 | + { |
| 233 | + ATLTRACE( "RRowsetUpdate::FlushData -- " |
| 234 | + "No filename specified\n"); |
| 235 | + return E_FAIL; |
| 236 | + } |
| 237 | + |
| 238 | + // Open the file |
| 239 | + _tcscpy_s(szFile, sizeOfFileName, OLE2T(m_strCommandText)); |
| 240 | + if ((szFile[0] == _T('\0')) || |
| 241 | + ((err = _tfopen_s(&pFile, &szFile[0], _T("w"))) != 0)) |
| 242 | + { |
| 243 | + ATLTRACE("RUpdateRowset::FlushData -- Could not open file\n"); |
| 244 | + return DB_E_NOTABLE; |
| 245 | + } |
| 246 | + |
| 247 | + // Iterate through the row data and store it. |
| 248 | + for (long l=0; l<m_rgRowData.GetSize(); l++) |
| 249 | + { |
| 250 | + CAgentMan am = m_rgRowData[l]; |
| 251 | + |
| 252 | + _putw((int)am.dwFixed, pFile); |
| 253 | + |
| 254 | + if (_tcscmp(&am.szCommand[0], _T("")) != 0) |
| 255 | + _stprintf_s(&szString[0], _T("%s\n"), am.szCommand); |
| 256 | + else |
| 257 | + _stprintf_s(&szString[0], _T("%s\n"), _T("NULL")); |
| 258 | + _fputts(szString, pFile); |
| 259 | + |
| 260 | + if (_tcscmp(&am.szText[0], _T("")) != 0) |
| 261 | + _stprintf_s(&szString[0], _T("%s\n"), am.szText); |
| 262 | + else |
| 263 | + _stprintf_s(&szString[0], _T("%s\n"), _T("NULL")); |
| 264 | + _fputts(szString, pFile); |
| 265 | + |
| 266 | + if (_tcscmp(&am.szCommand2[0], _T("")) != 0) |
| 267 | + _stprintf_s(&szString[0], _T("%s\n"), am.szCommand2); |
| 268 | + else |
| 269 | + _stprintf_s(&szString[0], _T("%s\n"), _T("NULL")); |
| 270 | + _fputts(szString, pFile); |
| 271 | + |
| 272 | + if (_tcscmp(&am.szText2[0], _T("")) != 0) |
| 273 | + _stprintf_s(&szString[0], _T("%s\n"), am.szText2); |
| 274 | + else |
| 275 | + _stprintf_s(&szString[0], _T("%s\n"), _T("NULL")); |
| 276 | + _fputts(szString, pFile); |
| 277 | + } |
| 278 | + |
| 279 | + if (fflush(pFile) == EOF || fclose(pFile) == EOF) |
| 280 | + { |
| 281 | + ATLTRACE("RRowsetUpdate::FlushData -- " |
| 282 | + "Couldn't flush or close file\n"); |
| 283 | + } |
| 284 | + |
| 285 | + return S_OK; |
| 286 | +} |
| 287 | +``` |
| 288 | +
|
| 289 | +### Handling Changes |
| 290 | +
|
| 291 | +For your provider to handle changes, you first need to make sure your data store (such as a text file or video file) has facilities that enable you to make changes on it. If it does not, you should create that code separately from the provider project. |
| 292 | +
|
| 293 | +### Handling NULL Data |
| 294 | +
|
| 295 | +It is possible that an end user will send NULL data. When you write NULL values to fields in the data source, there can be potential problems. Imagine an order-taking application that accepts values for city and postal code; it could accept either or both values, but not neither, because in that case delivery would be impossible. You therefore have to restrict certain combinations of NULL values in fields that make sense for your application. |
| 296 | +
|
| 297 | +As the provider developer, you have to consider how you will store that data, how you will read that data from the data store, and how you specify that to the user. Specifically, you must consider how to change the data status of rowset data in the data source (for example, DataStatus = NULL). You decide what value to return when a consumer accesses a field containing a NULL value. |
| 298 | +
|
| 299 | +Look at the code in the UpdatePV sample; it illustrates how a provider can handle NULL data. In UpdatePV, the provider stores NULL data by writing the string "NULL" in the data store. When it reads NULL data from the data store, it sees that string and then empties the buffer, creating a NULL string. It also has an override of `IRowsetImpl::GetDBStatus` in which it returns DBSTATUS_S_ISNULL if that data value is empty. |
| 300 | +
|
| 301 | +### Marking Nullable Columns |
| 302 | +If you also implement schema rowsets (see `IDBSchemaRowsetImpl`), your implementation should specify in the DBSCHEMA_COLUMNS rowset (usually marked in your provider by CxxxSchemaColSchemaRowset) that the column is nullable. |
| 303 | +
|
| 304 | +You also need to specify that all nullable columns contain the DBCOLUMNFLAGS_ISNULLABLE value in your version of the `GetColumnInfo`. |
| 305 | +
|
| 306 | +In the OLE DB templates implementation, if you fail to mark columns as nullable, the provider assumes that they must contain a value and will not allow the consumer to send it null values. |
| 307 | +
|
| 308 | +The following example shows how the `CommonGetColInfo` function is implemented in CUpdateCommand (see UpProvRS.cpp) in UpdatePV. Note how the columns have this DBCOLUMNFLAGS_ISNULLABLE for nullable columns. |
| 309 | +
|
| 310 | +```cpp |
| 311 | +///////////////////////////////////////////////////////////////////////////// |
| 312 | +// CUpdateCommand (in UpProvRS.cpp) |
| 313 | + |
| 314 | +ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG* pcCols, bool bBookmark) |
| 315 | +{ |
| 316 | + static ATLCOLUMNINFO _rgColumns[6]; |
| 317 | + ULONG ulCols = 0; |
| 318 | + |
| 319 | + if (bBookmark) |
| 320 | + { |
| 321 | + ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Bookmark"), 0, |
| 322 | + sizeof(DWORD), DBTYPE_BYTES, |
| 323 | + 0, 0, GUID_NULL, CAgentMan, dwBookmark, |
| 324 | + DBCOLUMNFLAGS_ISBOOKMARK) |
| 325 | + ulCols++; |
| 326 | + } |
| 327 | + |
| 328 | + // Next set the other columns up. |
| 329 | + // Add a fixed length entry for OLE DB conformance testing purposes |
| 330 | + ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Fixed"), 1, 4, DBTYPE_UI4, |
| 331 | + 10, 255, GUID_NULL, CAgentMan, dwFixed, |
| 332 | + DBCOLUMNFLAGS_WRITE | |
| 333 | + DBCOLUMNFLAGS_ISFIXEDLENGTH) |
| 334 | + ulCols++; |
| 335 | + |
| 336 | + ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Command"), 2, 16, DBTYPE_STR, |
| 337 | + 255, 255, GUID_NULL, CAgentMan, szCommand, |
| 338 | + DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE) |
| 339 | + ulCols++; |
| 340 | + ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Text"), 3, 16, DBTYPE_STR, |
| 341 | + 255, 255, GUID_NULL, CAgentMan, szText, |
| 342 | + DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE) |
| 343 | + ulCols++; |
| 344 | + |
| 345 | + ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Command2"), 4, 16, DBTYPE_STR, |
| 346 | + 255, 255, GUID_NULL, CAgentMan, szCommand2, |
| 347 | + DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE) |
| 348 | + ulCols++; |
| 349 | + ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Text2"), 5, 16, DBTYPE_STR, |
| 350 | + 255, 255, GUID_NULL, CAgentMan, szText2, |
| 351 | + DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE) |
| 352 | + ulCols++; |
| 353 | + |
| 354 | + if (pcCols != NULL) |
| 355 | + { |
| 356 | + *pcCols = ulCols; |
| 357 | + } |
| 358 | + |
| 359 | + return _rgColumns; |
| 360 | +} |
| 361 | +
|
| 362 | +``` |
| 363 | + |
| 364 | +### Default Values |
| 365 | + |
| 366 | +As with NULL data, you have the responsibility to deal with changing default values. |
| 367 | + |
| 368 | +The default of FlushData and Execute is to return S_OK. Therefore, if you do not override this function, the changes appear to succeed (S_OK will be returned), but they will not be transmitted to the data store. |
| 369 | + |
| 370 | +In the UpdatePV sample (in Rowset.h), the `SetDBStatus` method handles default values as follows: |
| 371 | + |
| 372 | +```cpp |
| 373 | +virtual HRESULT SetDBStatus(DBSTATUS* pdbStatus, CSimpleRow* pRow, |
| 374 | + ATLCOLUMNINFO* pColInfo) |
| 375 | +{ |
| 376 | + ATLASSERT(pRow != NULL && pColInfo != NULL && pdbStatus != NULL); |
| 377 | + |
| 378 | + void* pData = NULL; |
| 379 | + char* pDefaultData = NULL; |
| 380 | + DWORD* pFixedData = NULL; |
| 381 | + |
| 382 | + switch (*pdbStatus) |
| 383 | + { |
| 384 | + case DBSTATUS_S_DEFAULT: |
| 385 | + pData = (void*)&m_rgRowData[pRow->m_iRowset]; |
| 386 | + if (pColInfo->wType == DBTYPE_STR) |
| 387 | + { |
| 388 | + pDefaultData = (char*)pData + pColInfo->cbOffset; |
| 389 | + strcpy_s(pDefaultData, "Default"); |
| 390 | + } |
| 391 | + else |
| 392 | + { |
| 393 | + pFixedData = (DWORD*)((BYTE*)pData + |
| 394 | + pColInfo->cbOffset); |
| 395 | + *pFixedData = 0; |
| 396 | + return S_OK; |
| 397 | + } |
| 398 | + break; |
| 399 | + case DBSTATUS_S_ISNULL: |
| 400 | + default: |
| 401 | + break; |
| 402 | + } |
| 403 | + return S_OK; |
| 404 | +} |
| 405 | +``` |
| 406 | +
|
| 407 | +### Column Flags |
| 408 | +
|
| 409 | +If you support default values on your columns, you need to set it using metadata in the \<provider class\>SchemaRowset class. Set `m_bColumnHasDefault` = VARIANT_TRUE. |
| 410 | +
|
| 411 | +You also have the responsibility to set the column flags, which are specified using the DBCOLUMNFLAGS enumerated type. The column flags describe column characteristics. |
| 412 | +
|
| 413 | +For example, in the `CUpdateSessionColSchemaRowset` class in UpdatePV (in Session.h), the first column is set up this way: |
| 414 | +
|
| 415 | +```cpp |
| 416 | +// Set up column 1 |
| 417 | +trData[0].m_ulOrdinalPosition = 1; |
| 418 | +trData[0].m_bIsNullable = VARIANT_FALSE; |
| 419 | +trData[0].m_bColumnHasDefault = VARIANT_TRUE; |
| 420 | +trData[0].m_nDataType = DBTYPE_UI4; |
| 421 | +trData[0].m_nNumericPrecision = 10; |
| 422 | +trData[0].m_ulColumnFlags = DBCOLUMNFLAGS_WRITE | |
| 423 | + DBCOLUMNFLAGS_ISFIXEDLENGTH; |
| 424 | +lstrcpyW(trData[0].m_szColumnDefault, OLESTR("0")); |
| 425 | +m_rgRowData.Add(trData[0]); |
| 426 | +``` |
| 427 | + |
| 428 | +This code specifies, among other things, that the column supports a default value of 0, that it be writeable, and that all data in the column have the same length. If you want the data in a column to have variable length, you would not set this flag. |
| 429 | + |
| 430 | +## See Also |
| 431 | +[Creating an OLE DB Provider](creating-an-ole-db-provider.md) |
0 commit comments