Skip to content

Commit 1f651d6

Browse files
author
Michael Blome
committed
restored oledb page screwed up previously
1 parent 559b277 commit 1f651d6

File tree

1 file changed

+296
-1
lines changed

1 file changed

+296
-1
lines changed

docs/data/oledb/creating-an-updatable-provider.md

Lines changed: 296 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Creating an Updatable Provider | Microsoft Docs"
33
ms.custom: ""
4-
ms.date: "11/04/2016"
4+
ms.date: "08/16/2018"
55
ms.technology: ["cpp-data"]
66
ms.topic: "reference"
77
dev_langs: ["C++"]
@@ -134,3 +134,298 @@ Visual C++ supports updatable providers or providers that can update (write to)
134134
## <a name="vchowwritingtothedatasource"></a> Writing to the Data Source
135135
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.)
136136

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

Comments
 (0)