Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Program Visual C++ obsługuje aktualizowalnych dostawców lub dostawców, którzy mogą aktualizować (zapisywać) magazyn danych. W tym temacie omówiono sposób tworzenia aktualizowalnych dostawców przy użyciu szablonów OLE DB.
W tym temacie założono, że zaczynasz od dostawcy możliwego do działania. Istnieją dwa kroki tworzenia możliwego do zaktualizowania dostawcy. Najpierw musisz zdecydować, w jaki sposób dostawca wprowadzi zmiany w magazynie danych; niezależnie od tego, czy zmiany mają być wykonywane natychmiast, czy odroczone do momentu wydania polecenia aktualizacji. W sekcji "Making Providers Updatable" opisano zmiany i ustawienia, które należy wykonać w kodzie dostawcy.
Następnie należy upewnić się, że dostawca zawiera wszystkie funkcje obsługi wszystkich elementów, których odbiorca może zażądać. Jeśli użytkownik chce zaktualizować magazyn danych, dostawca musi zawierać kod, który utrwala dane w magazynie danych. Na przykład możesz użyć biblioteki czasu wykonywania języka C lub MFC do wykonywania takich operacji na źródle danych. W sekcji "Zapisywanie w źródle danych" opisano, jak zapisywać w źródle danych, zajmować się wartościami NULL i wartościami domyślnymi oraz ustawiać flagi kolumn.
Note
UpdatePV is an example of an updatable provider. Protokół UpdatePV jest taki sam jak MyProv, ale z obsługą aktualizowalną.
Tworzenie dostawców z możliwością aktualizowania
Kluczem do aktualizowania dostawcy jest zrozumienie, jakie operacje mają być wykonywane przez dostawcę w magazynie danych oraz sposób wykonywania tych operacji przez dostawcę. W szczególności głównym problemem jest to, czy aktualizacje magazynu danych mają być wykonywane natychmiast, czy odroczone (wsadowe) do momentu wydania polecenia aktualizacji.
Najpierw musisz zdecydować, czy dziedziczyć z IRowsetChangeImpl klasy zestawu wierszy, czy IRowsetUpdateImpl też z nich. W zależności od tego, które z tych metod należy zaimplementować, działanie trzech metod będzie miało wpływ na: SetData, InsertRowsi DeleteRows.
If you inherit from IRowsetChangeImpl, calling these three methods immediately changes the data store.
If you inherit from IRowsetUpdateImpl, the methods defer changes to the data store until you call
Update,GetOriginalData, orUndo. Jeśli aktualizacja obejmuje kilka zmian, są one wykonywane w trybie wsadowym (należy pamiętać, że zmiany wsadowe mogą zwiększyć obciążenie pamięci).
Należy pamiętać, że IRowsetUpdateImpl pochodzi z klasy IRowsetChangeImpl.
IRowsetUpdateImpl W związku z tym zapewnia możliwość zmiany oraz możliwości wsadowe.
Aby zapewnić aktualność dostawcy
W klasie zestawu wierszy dziedzicz z
IRowsetChangeImplklasy lubIRowsetUpdateImpl. Te klasy zapewniają odpowiednie interfejsy do zmiany magazynu danych:Adding IRowsetChange
Dodaj
IRowsetChangeImpldo łańcucha dziedziczenia przy użyciu tego formularza:IRowsetChangeImpl< rowset-name, storage-name >COM_INTERFACE_ENTRY(IRowsetChange)Dodaj również doBEGIN_COM_MAPsekcji w klasie zestawu wierszy.Adding IRowsetUpdate
Dodaj
IRowsetUpdatedo łańcucha dziedziczenia przy użyciu tego formularza:IRowsetUpdateImpl< rowset-name, storage>Note
Należy usunąć
IRowsetChangeImplwiersz z łańcucha dziedziczenia. Ten jeden wyjątek od wspomnianej wcześniej dyrektywy musi zawierać kod .IRowsetChangeImplDodaj następujące elementy do mapy MODELU COM (
BEGIN_COM_MAP ... END_COM_MAP):W przypadku implementacji Dodaj do mapy MODELU COM IRowsetChangeImplCOM_INTERFACE_ENTRY(IRowsetChange)IRowsetUpdateImplCOM_INTERFACE_ENTRY(IRowsetUpdate)W przypadku implementacji Dodaj do mapy zestawu właściwości IRowsetChangeImplPROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)IRowsetUpdateImplPROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)W poleceniu dodaj następujące elementy do mapy zestawu właściwości (
BEGIN_PROPSET_MAP ... END_PROPSET_MAP):W przypadku implementacji Dodaj do mapy zestawu właściwości IRowsetChangeImplPROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)IRowsetUpdateImplPROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)PROPERTY_INFO_ENTRY_VALUE(IRowsetUpdate, VARIANT_FALSE)Na mapie zestawu właściwości należy również uwzględnić wszystkie następujące ustawienia, jak pokazano poniżej:
PROPERTY_INFO_ENTRY_VALUE(UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE) PROPERTY_INFO_ENTRY_VALUE(CHANGEINSERTEDROWS, VARIANT_TRUE) PROPERTY_INFO_ENTRY_VALUE(IMMOBILEROWS, VARIANT_TRUE) PROPERTY_INFO_ENTRY_EX(OWNINSERT, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(OWNUPDATEDELETE, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(OTHERINSERT, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(OTHERUPDATEDELETE, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_TRUE, 0) PROPERTY_INFO_ENTRY_EX(REMOVEDELETED, VT_BOOL, DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ, VARIANT_FALSE, 0)Wartości używane w tych wywołaniach makr można znaleźć w pliku Atldb.h dla identyfikatorów właściwości i wartości (jeśli atldb.h różni się od dokumentacji online, atldb.h zastępuje dokumentację).
Note
VARIANT_FALSEWiele ustawień iVARIANT_TRUEjest wymaganych przez szablony OLE DB. Specyfikacja OLE DB mówi, że mogą być odczytywane/zapisywane, ale szablony OLE DB mogą obsługiwać tylko jedną wartość.W przypadku implementacji interfejsu IRowsetChangeImpl
W przypadku implementacji
IRowsetChangeImplprogramu należy ustawić następujące właściwości dostawcy. Te właściwości są używane głównie do żądania interfejsów za pośrednictwem .ICommandProperties::SetPropertiesDBPROP_IRowsetChange: Ustawienie tego ustawienia automatycznie ustawia wartośćDBPROP_IRowsetChange.DBPROP_UPDATABILITY: Maska bitów określająca obsługiwane metody na :IRowsetChangeSetData,DeleteRowslubInsertRow.DBPROP_CHANGEINSERTEDROWS: Użytkownik może wywołaćIRowsetChange::DeleteRowslubSetDatadla nowo wstawionych wierszy.DBPROP_IMMOBILEROWS: Zestaw wierszy nie zmieni kolejności wstawionych ani zaktualizowanych wierszy.
W przypadku implementowania interfejsu IRowsetUpdateImpl
Jeśli zaimplementujesz
IRowsetUpdateImplprogram , musisz ustawić następujące właściwości u dostawcy, oprócz ustawienia wszystkich właściwości dlaIRowsetChangeImplpoprzednio wymienionych:DBPROP_IRowsetUpdate.DBPROP_OWNINSERT: musi być READ_ONLY i VARIANT_TRUE.DBPROP_OWNUPDATEDELETE: musi być READ_ONLY i VARIANT_TRUE.DBPROP_OTHERINSERT: musi być READ_ONLY i VARIANT_TRUE.DBPROP_OTHERUPDATEDELETE: musi być READ_ONLY i VARIANT_TRUE.DBPROP_REMOVEDELETED: musi być READ_ONLY i VARIANT_TRUE.DBPROP_MAXPENDINGROWS.
Note
Jeśli obsługujesz powiadomienia, możesz również mieć inne właściwości; zobacz sekcję na
IRowsetNotifyCPtej liście.
Zapisywanie w źródle danych
Aby odczytać ze źródła danych, wywołaj Execute funkcję . Aby zapisać w źródle danych, wywołaj FlushData funkcję . (Ogólnie rzecz biorąc, opróżnianie oznacza zapisanie modyfikacji w tabeli lub indeksie na dysku).
FlushData(HROW, HACCESSOR);
Argumenty uchwytu wiersza (HROW) i uchwytu dostępu (HACCESSOR) umożliwiają określenie regionu do zapisu. Zazwyczaj jedno pole danych jest zapisywane jednocześnie.
Metoda FlushData zapisuje dane w formacie, w którym pierwotnie był przechowywany. Jeśli ta funkcja nie zostanie zastąpiona, dostawca będzie działać poprawnie, ale zmiany nie zostaną opróżnione do magazynu danych.
Kiedy należy opróżnić
Szablony dostawców wywołają funkcję FlushData za każdym razem, gdy dane muszą być zapisywane w magazynie danych; zwykle (ale nie zawsze) występuje w wyniku wywołań do następujących funkcji:
IRowsetChange::DeleteRowsIRowsetChange::SetDataIRowsetChange::InsertRows(jeśli istnieją nowe dane do wstawienia w wierszu)IRowsetUpdate::Update
Zasady działania
Użytkownik wykonuje wywołanie, które wymaga opróżnienia (takiego jak aktualizacja), a to wywołanie jest przekazywane do dostawcy, co zawsze wykonuje następujące czynności:
Wywołuje wywołania
SetDBStatusza każdym razem, gdy masz powiązaną wartość stanu.Sprawdza flagi kolumn.
Wywołuje
IsUpdateAllowed.
Te trzy kroki pomagają zapewnić bezpieczeństwo. Następnie dostawca wywołuje metodę FlushData.
How to Implement FlushData
Aby zaimplementować usługę FlushData, należy wziąć pod uwagę kilka problemów:
Upewnij się, że magazyn danych może obsługiwać zmiany.
Obsługa wartości NULL.
Obsługa wartości domyślnych
Aby zaimplementować własną FlushData metodę, musisz:
Przejdź do klasy zestawu wierszy.
W klasie zestawu wierszy umieść deklarację:
HRESULT FlushData(HROW, HACCESSOR) { // Insert your implementation here and return an HRESULT. }Podaj implementację elementu
FlushData.
Dobra implementacja FlushData magazynów zawiera tylko wiersze i kolumny, które są rzeczywiście aktualizowane. Możesz użyć parametrów HROW i HACCESSOR, aby określić bieżący wiersz i kolumnę przechowywaną do optymalizacji.
Zazwyczaj największym wyzwaniem jest praca z własnym natywnym magazynem danych. Jeśli to możliwe, spróbuj wykonać:
Zachowaj metodę zapisywania w magazynie danych tak proste, jak to możliwe.
Obsługa wartości NULL (opcjonalne, ale zalecane).
Obsługa wartości domyślnych (opcjonalne, ale zalecane).
Najlepszym rozwiązaniem jest posiadanie rzeczywistych określonych wartości w magazynie danych dla wartości NULL i wartości domyślnych. Najlepszym rozwiązaniem jest ekstrapolowanie tych danych. Jeśli nie, zaleca się, aby nie zezwalać na wartości NULL i wartości domyślne.
Poniższy przykład przedstawia sposób FlushData implementacji RUpdateRowset w klasie w przykładzie UpdatePV (zobacz Rowset.h w przykładowym kodzie):
///////////////////////////////////////////////////////////////////////////
// class RUpdateRowset (in rowset.h)
...
HRESULT FlushData(HROW, HACCESSOR)
{
ATLTRACE2(atlTraceDBProvider, 0, "RUpdateRowset::FlushData\n");
USES_CONVERSION;
enum {
sizeOfString = 256,
sizeOfFileName = MAX_PATH
};
FILE* pFile = NULL;
TCHAR szString[sizeOfString];
TCHAR szFile[sizeOfFileName];
errcode err = 0;
ObjectLock lock(this);
// From a filename, passed in as a command text,
// scan the file placing data in the data array.
if (m_strCommandText == (BSTR)NULL)
{
ATLTRACE( "RRowsetUpdate::FlushData -- "
"No filename specified\n");
return E_FAIL;
}
// Open the file
_tcscpy_s(szFile, sizeOfFileName, OLE2T(m_strCommandText));
if ((szFile[0] == _T('\0')) ||
((err = _tfopen_s(&pFile, &szFile[0], _T("w"))) != 0))
{
ATLTRACE("RUpdateRowset::FlushData -- Could not open file\n");
return DB_E_NOTABLE;
}
// Iterate through the row data and store it.
for (long l=0; l<m_rgRowData.GetSize(); l++)
{
CAgentMan am = m_rgRowData[l];
_putw((int)am.dwFixed, pFile);
if (_tcscmp(&am.szCommand[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szCommand);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
if (_tcscmp(&am.szText[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szText);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
if (_tcscmp(&am.szCommand2[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szCommand2);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
if (_tcscmp(&am.szText2[0], _T("")) != 0)
_stprintf_s(&szString[0], _T("%s\n"), am.szText2);
else
_stprintf_s(&szString[0], _T("%s\n"), _T("NULL"));
_fputts(szString, pFile);
}
if (fflush(pFile) == EOF || fclose(pFile) == EOF)
{
ATLTRACE("RRowsetUpdate::FlushData -- "
"Couldn't flush or close file\n");
}
return S_OK;
}
Handling Changes
Aby dostawca obsługiwał zmiany, należy najpierw upewnić się, że magazyn danych (taki jak plik tekstowy lub plik wideo) ma narzędzia umożliwiające wprowadzanie w nim zmian. Jeśli tak nie jest, należy utworzyć ten kod oddzielnie od projektu dostawcy.
Obsługa danych NULL
Istnieje możliwość, że użytkownik końcowy wyśle dane o wartości NULL. Podczas zapisywania wartości NULL w polach w źródle danych mogą występować potencjalne problemy. Wyobraź sobie aplikację do przyjmowania zamówień, która akceptuje wartości dla miasta i kodu pocztowego; może zaakceptować albo obie wartości, ale nie, ponieważ w takim przypadku dostawa byłaby niemożliwa. W związku z tym należy ograniczyć pewne kombinacje wartości NULL w polach, które mają sens dla aplikacji.
Jako deweloper dostawcy musisz wziąć pod uwagę sposób przechowywania tych danych, sposobu odczytywania tych danych z magazynu danych oraz sposobu określania ich użytkownikowi. W szczególności należy rozważyć zmianę stanu danych zestawu wierszy w źródle danych (na przykład DataStatus = NULL). Decydujesz, jaka wartość ma być zwracana, gdy użytkownik uzyskuje dostęp do pola zawierającego wartość NULL.
Przyjrzyj się kodowi w przykładzie UpdatePV; Ilustruje to, jak dostawca może obsługiwać dane NULL. W usłudze UpdatePV dostawca przechowuje dane o wartości NULL, zapisując ciąg "NULL" w magazynie danych. Gdy odczytuje dane o wartości NULL z magazynu danych, zobaczy ten ciąg, a następnie opróżni bufor, tworząc ciąg NULL. Ma również przesłonięcia IRowsetImpl::GetDBStatus , w którym zwraca DBSTATUS_S_ISNULL, jeśli ta wartość danych jest pusta.
Oznaczanie kolumn dopuszczanych do wartości null
Jeśli implementujesz również zestawy wierszy schematu (zobacz IDBSchemaRowsetImpl), implementacja powinna być określona w zestawie wierszy DBSCHEMA_COLUMNS (zwykle oznaczonym u dostawcy przez CxxxSchemaColSchemaRowset), że kolumna ma wartość null.
Należy również określić, że wszystkie kolumny dopuszczające wartość null zawierają wartość DBCOLUMNFLAGS_ISNULLABLE w wersji elementu GetColumnInfo.
W implementacji szablonów OLE DB, jeśli nie można oznaczyć kolumn jako dopuszczających wartość null, dostawca zakłada, że musi zawierać wartość i nie zezwoli użytkownikowi na wysyłanie jej wartości null.
W poniższym przykładzie pokazano, jak CommonGetColInfo funkcja jest implementowana w CUpdateCommand (zobacz UpProvRS.cpp) w updatePV. Zwróć uwagę, jak kolumny mają tę DBCOLUMNFLAGS_ISNULLABLE dla kolumn dopuszczanych do wartości null.
/////////////////////////////////////////////////////////////////////////////
// CUpdateCommand (in UpProvRS.cpp)
ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG* pcCols, bool bBookmark)
{
static ATLCOLUMNINFO _rgColumns[6];
ULONG ulCols = 0;
if (bBookmark)
{
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Bookmark"), 0,
sizeof(DWORD), DBTYPE_BYTES,
0, 0, GUID_NULL, CAgentMan, dwBookmark,
DBCOLUMNFLAGS_ISBOOKMARK)
ulCols++;
}
// Next set the other columns up.
// Add a fixed length entry for OLE DB conformance testing purposes
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Fixed"), 1, 4, DBTYPE_UI4,
10, 255, GUID_NULL, CAgentMan, dwFixed,
DBCOLUMNFLAGS_WRITE |
DBCOLUMNFLAGS_ISFIXEDLENGTH)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Command"), 2, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szCommand,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Text"), 3, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szText,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Command2"), 4, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szCommand2,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Text2"), 5, 16, DBTYPE_STR,
255, 255, GUID_NULL, CAgentMan, szText2,
DBCOLUMNFLAGS_WRITE | DBCOLUMNFLAGS_ISNULLABLE)
ulCols++;
if (pcCols != NULL)
{
*pcCols = ulCols;
}
return _rgColumns;
}
Default Values
Podobnie jak w przypadku danych NULL, ponosisz odpowiedzialność za zmianę wartości domyślnych.
Wartością domyślną FlushData i Execute jest zwracanie S_OK. W związku z tym, jeśli ta funkcja nie zostanie zastąpiona, zmiany pojawią się pomyślnie (S_OK zostaną zwrócone), ale nie zostaną przesłane do magazynu danych.
W przykładzie (w pliku UpdatePV Rowset.h) SetDBStatus metoda obsługuje wartości domyślne w następujący sposób:
virtual HRESULT SetDBStatus(DBSTATUS* pdbStatus, CSimpleRow* pRow,
ATLCOLUMNINFO* pColInfo)
{
ATLASSERT(pRow != NULL && pColInfo != NULL && pdbStatus != NULL);
void* pData = NULL;
char* pDefaultData = NULL;
DWORD* pFixedData = NULL;
switch (*pdbStatus)
{
case DBSTATUS_S_DEFAULT:
pData = (void*)&m_rgRowData[pRow->m_iRowset];
if (pColInfo->wType == DBTYPE_STR)
{
pDefaultData = (char*)pData + pColInfo->cbOffset;
strcpy_s(pDefaultData, "Default");
}
else
{
pFixedData = (DWORD*)((BYTE*)pData +
pColInfo->cbOffset);
*pFixedData = 0;
return S_OK;
}
break;
case DBSTATUS_S_ISNULL:
default:
break;
}
return S_OK;
}
Column Flags
Jeśli w kolumnach są obsługiwane wartości domyślne, należy ustawić je przy użyciu metadanych w <klasie>provider Class SchemaRowset. Ustaw wartość m_bColumnHasDefault = VARIANT_TRUE.
Masz również obowiązek ustawić flagi kolumn, które są określone przy użyciu wyliczonego typu DBCOLUMNFLAGS. Flagi kolumn opisują charakterystykę kolumn.
Na przykład w CUpdateSessionColSchemaRowset klasie w ( UpdatePV w session.h) pierwsza kolumna jest skonfigurowana w następujący sposób:
// Set up column 1
trData[0].m_ulOrdinalPosition = 1;
trData[0].m_bIsNullable = VARIANT_FALSE;
trData[0].m_bColumnHasDefault = VARIANT_TRUE;
trData[0].m_nDataType = DBTYPE_UI4;
trData[0].m_nNumericPrecision = 10;
trData[0].m_ulColumnFlags = DBCOLUMNFLAGS_WRITE |
DBCOLUMNFLAGS_ISFIXEDLENGTH;
lstrcpyW(trData[0].m_szColumnDefault, OLESTR("0"));
m_rgRowData.Add(trData[0]);
Ten kod określa między innymi, że kolumna obsługuje domyślną wartość 0, którą można zapisywać, a wszystkie dane w kolumnie mają taką samą długość. Jeśli chcesz, aby dane w kolumnie miały zmienną długość, nie ustawisz tej flagi.