Tworzenie aktualizowalnego dostawcy
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.
Uwaga
UpdatePV jest przykładem możliwego do zaktualizowania dostawcy. 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
, InsertRows
i DeleteRows
.
Jeśli dziedziczysz z IRowsetChangeImpl, wywołanie tych trzech metod natychmiast zmienia magazyn danych.
Jeśli dziedziczysz z IRowsetUpdateImpl, metody odroczy zmiany w magazynie danych do momentu wywołania
Update
metody ,GetOriginalData
lubUndo
. 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
IRowsetChangeImpl
klasy lubIRowsetUpdateImpl
. Te klasy zapewniają odpowiednie interfejsy do zmiany magazynu danych:Dodawanie elementu IRowsetChange
Dodaj
IRowsetChangeImpl
do łańcucha dziedziczenia przy użyciu tego formularza:IRowsetChangeImpl< rowset-name, storage-name >
COM_INTERFACE_ENTRY(IRowsetChange)
Dodaj również doBEGIN_COM_MAP
sekcji w klasie zestawu wierszy.Dodawanie elementu IRowsetUpdate
Dodaj
IRowsetUpdate
do łańcucha dziedziczenia przy użyciu tego formularza:IRowsetUpdateImpl< rowset-name, storage>
Uwaga
Należy usunąć
IRowsetChangeImpl
wiersz z łańcucha dziedziczenia. Ten jeden wyjątek od wspomnianej wcześniej dyrektywy musi zawierać kod .IRowsetChangeImpl
Dodaj następujące elementy do mapy MODELU COM (
BEGIN_COM_MAP ... END_COM_MAP
):W przypadku implementacji Dodaj do mapy MODELU COM IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetUpdate)
W przypadku implementacji Dodaj do mapy zestawu właściwości IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_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 IRowsetChangeImpl
PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_FALSE)
IRowsetUpdateImpl
PROPERTY_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ę).
Uwaga
VARIANT_FALSE
Wiele ustawień iVARIANT_TRUE
jest 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
IRowsetChangeImpl
programu 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::SetProperties
DBPROP_IRowsetChange
: Ustawienie tego ustawienia automatycznie ustawia wartośćDBPROP_IRowsetChange
.DBPROP_UPDATABILITY
: Maska bitów określająca obsługiwane metody na :IRowsetChange
SetData
,DeleteRows
lubInsertRow
.DBPROP_CHANGEINSERTEDROWS
: Użytkownik może wywołaćIRowsetChange::DeleteRows
lubSetData
dla nowo wstawionych wierszy.DBPROP_IMMOBILEROWS
: Zestaw wierszy nie zmieni kolejności wstawionych ani zaktualizowanych wierszy.
W przypadku implementowania interfejsu IRowsetUpdateImpl
Jeśli zaimplementujesz
IRowsetUpdateImpl
program , musisz ustawić następujące właściwości u dostawcy, oprócz ustawienia wszystkich właściwości dlaIRowsetChangeImpl
poprzednio 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
.
Uwaga
Jeśli obsługujesz powiadomienia, możesz również mieć inne właściwości; zobacz sekcję na
IRowsetNotifyCP
tej 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::DeleteRows
IRowsetChange::SetData
IRowsetChange::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
SetDBStatus
za 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;
}
Obsługa zmian
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;
}
Wartości domyślne
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;
}
Flagi kolumn
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.
Zobacz też
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla