Tworzenie aktualizowalna dostawcy
Visual C++ 6.0 są obsługiwane tylko dostawców tylko do odczytu.Visual C++.NET obsługuje aktualizowalna dostawców lub dostawców, które można aktualizować (zapisać) w magazynie danych.W tym temacie omówiono sposób tworzenia aktualizowalna dostawców za pomocą szablonów OLE DB.
W tym temacie założono, że rozpoczyna się wykonalne dostawcy.Istnieją dwa kroki tworzenia aktualizowalna dostawcy.Najpierw należy zdecydować, jaki dostawca wprowadzić zmiany do magazynu danych; w szczególności czy zmiany niezwłocznie realizowane są lub odłożone do momentu wydania polecenia Aktualizuj.Sekcja "Dokonywania aktualizowalna dostawców" opisano zmiany i ustawienia, należy wykonać kod dostawcy.
Następnie należy należy upewnić się, czy dostawca zawiera wszystkie funkcje do obsługi coś konsument może zażądać jej.Jeśli konsument chce zaktualizować magazyn danych, dostawca musi zawierać kod, który będzie się powtarzał danych w magazynie danych.Na przykład biblioteka uruchomieniowa c lub MFC może użyć do wykonywania takich operacji na źródle danych.Sekcja "piśmie ze źródłem danych" opisano sposób zapisu do źródła danych, zajmuje NULL wartości domyślne i ustawić flagi kolumny.
[!UWAGA]
UpdatePV jest przykładem aktualizowalna dostawcy.UpdatePV jest taka sama, jak MyProv, ale z obsługą aktualizowalna.
Tworzenie dostawcy aktualizowalna
Dostawcy umożliwiają aktualizowalna jest zrozumienie działania, jakie ma dostawcy do wykonania na magazynu danych i sposobu dostawca przeprowadzenia tych operacji.W szczególności największym problemem jest, czy są aktualizacje do magazynu danych Sporządzono natychmiast lub odroczone (batched) do momentu wydania polecenia Aktualizuj.
Najpierw należy podjąć decyzję o dziedziczą z IRowsetChangeImpl lub IRowsetUpdateImpl w klasie zestawu wierszy.W zależności od tego, która z nich wybierzesz wykonania, dotyczy funkcji trzy metody: SetData, InsertRows, i DeleteRows.
Jeśli dziedziczą z IRowsetChangeImpl, wywołanie tych trzech metod niezwłocznie zmienia magazynu danych.
Jeśli dziedziczą z IRowsetUpdateImpl, metody odroczyć zmian do magazynu danych do czasu wywołania Aktualizacja, GetOriginalData, lub Cofnij.Jeśli aktualizacja obejmuje kilka zmian, są wykonywane w trybie wsadowym (należy zauważyć, że tworzenie pakietów wsadowych zmian można dodać znaczne pamięci).
Należy zauważyć, że IRowsetUpdateImpl pochodzi z IRowsetChangeImpl.W ten sposób IRowsetUpdateImpl daje zmiana zdolności plus zdolności partii.
Do obsługi aktualizacji w dostawcy
W klasie zestawu wierszy, dziedziczą z IRowsetChangeImpl lub IRowsetUpdateImpl.Klasy te przewidują odpowiednich interfejsów zmiana magazynu danych:
Dodawanie IRowsetChange
Dodaj IRowsetChangeImpl na łańcuchu dziedziczenia, za pomocą tego formularza:
IRowsetChangeImpl< rowset-name, storage-name >
Również dodać COM_INTERFACE_ENTRY(IRowsetChange) do BEGIN_COM_MAP sekcji w klasie zestawu wierszy.
Dodawanie IRowsetUpdate
Dodaj IRowsetUpdate na łańcuchu dziedziczenia, za pomocą tego formularza:
IRowsetUpdateImpl< rowset-name, storage>
[!UWAGA]
Należy usunąć IRowsetChangeImpl linii od użytkownika łańcucha dziedziczenia.Ten wyjątek do dyrektywy wymienionej wcześniej musi zawierać kod IRowsetChangeImpl.
Dodaj do mapy COM (BEGIN_COM_MAP... END_COM_MAP):
W przypadku zastosowania
Dodać do mapy COM
IRowsetChangeImpl
COM_INTERFACE_ENTRY(IRowsetChange)
IRowsetUpdateImpl
COM_INTERFACE_ENTRY(IRowsetChange)COM_INTERFACE_ENTRY(IRowsetUpdate)
Polecenia, należy dodać następujące do mapy zestaw właściwości (BEGIN_PROPSET_MAP... END_PROPSET_MAP):
W przypadku zastosowania
Dodać do mapy zestaw 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 zestaw właściwości należy także uwzględnić wszystkie następujące ustawienia pojawiających się 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 wywołania tych makr, patrząc w Atldb.h dla identyfikatorów właściwości i wartości można znaleźć (jeśli Atldb.h różni się od dokumentacji online, Atldb.h zastępuje dokumentacji).
[!UWAGA]
Wiele z VARIANT_FALSE i VARIANT_TRUE ustawienia są wymagane przez szablonów OLE DB; specyfikacja OLE DB mówi mogą być odczytu i zapisu, ale szablonów OLE DB może obsługiwać tylko jedną wartość.
Jeśli wdrożenie IRowsetChangeImpl
W przypadku zastosowania IRowsetChangeImpl, należy ustawić następujące właściwości na dostawcę.Właściwości te są używane głównie do żądania interfejsów poprzez ICommandProperties::SetProperties.
DBPROP_IRowsetChange: Ustawienie to automatycznie ustawia DBPROP_IRowsetChange.
DBPROP_UPDATABILITY: Maska bitowa określająca obsługiwane metody na IRowsetChange: SetData, DeleteRows, lub InsertRow.
DBPROP_CHANGEINSERTEDROWS: Konsument może wywołać IRowsetChange::DeleteRows lub SetData nowo wstawionych wierszy.
DBPROP_IMMOBILEROWS: Zestaw wierszy nie spowoduje zmiany kolejności wiersze wstawione lub zaktualizowane.
Jeśli wdrożenie IRowsetUpdateImpl
W przypadku zastosowania IRowsetUpdateImpl, należy ustawić następujące właściwości na dostawcę, dodatkowo do ustawiania wszystkich właściwości dla IRowsetChangeImpl poprzednio wymienione:
DBPROP_IRowsetUpdate.
DBPROP_OWNINSERT: Musi być TYLKO_DO_ODCZYTU I VARIANT_TRUE.
DBPROP_OWNUPDATEDELETE: Musi być TYLKO_DO_ODCZYTU I VARIANT_TRUE.
DBPROP_OTHERINSERT: Musi być TYLKO_DO_ODCZYTU I VARIANT_TRUE.
DBPROP_OTHERUPDATEDELETE: Musi być TYLKO_DO_ODCZYTU I VARIANT_TRUE.
DBPROP_REMOVEDELETED: Musi być TYLKO_DO_ODCZYTU I VARIANT_TRUE.
DBPROP_MAXPENDINGROWS.
[!UWAGA]
Jeśli obsługiwane powiadomienia mogą zawierać również niektórych innych właściwości sekcja w IRowsetNotifyCP dla tej listy.
Na przykład z Ustawianie właściwości, zobacz właściwość ustawić mapę w CUpdateCommand (w Rowset.h) w UpdatePV.
Zapisywanie do źródła danych
Odczytać z źródła danych, należy wywołać Execute funkcji.Zapisać się do źródła danych, należy wywołać FlushData funkcji.(W sensie ogólnym, opróżnić środki, aby zapisać zmiany dokonane w tabeli lub indeksu na dysku).
FlushData(HROW, HACCESSOR);
Dojście do wiersza (HROW) i uchwyt akcesor (HACCESSOR) argumenty umożliwiają określenie regionu do zapisu.Zazwyczaj zapisu jedno pole danych w czasie.
FlushData Metoda zapisuje dane w formacie, w którym był oryginalnie zapisany.Ta funkcja nie zastępują, dostawca będzie działać poprawnie, ale zmian nie będzie opróżniany z magazynem danych.
Podczas opróżniania
Wywołanie dostawca szablonów FlushData w każdym przypadku, gdy dane musi być zapisany w magazynie danych; to zwykle (ale nie zawsze) występuje w wyniku wywołania następujące funkcje:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows (jeśli istnieje nowe dane do wstawiania w wierszu)
IRowsetUpdate::Update
Jak działa
Konsument wykonuje wywołania wymagającej koloru (takie jak Aktualizacja) i to wywołanie jest przekazywana do dostawcy, który zawsze wykonuje następujące czynności:
Wywołania SetDBStatus kiedy mają wartość stanu związany (zobacz OLE DB programistów odniesienia, rozdział 6, części danych: stan).
Sprawdza, czy kolumna flag.
Calls IsUpdateAllowed.
Te trzy kroki pomagają zabezpieczeń.Następnie rozmowy dostawca FlushData.
Jak FlushData wdrożenie
Aby zaimplementować FlushData, należy wziąć pod uwagę kilka problemów:
Upewnienie się, że magazyn danych może obsłużyć zmian.
Obsługa NULL wartości.
Obsługa wartości domyślne.
Aby zaimplementować własną FlushData metody, trzeba:
Przejdź do swojej klasy zestawu wierszy.
W zestawie wierszy klasy umieścić deklaracji:
HRESULT FlushData(HROW, HACCESSOR)
{
// Insert your implementation here and return an HRESULT.
}
- Zapewnienia wykonania FlushData.
Dobrą implementację FlushData przechowuje wierszy i kolumn, które faktycznie zostały zaktualizowane.Można użyć HROW i HACCESSOR parametrów do określenia bieżącego wiersza i kolumny, które są przechowywane na optymalizację.
Zazwyczaj największym wyzwaniem jest praca z magazynu danych w trybie macierzystym.Jeśli to możliwe spróbuj:
Zachowaj metody zapisywania do magazynu danych jak najprostsze.
Uchwyt NULL wartości (opcjonalne, ale zalecane).
Uchwyt wartości domyślne (opcjonalne, ale zalecane).
Doskonała jest rzeczywiste wartości określonej w magazynie danych dla NULL i wartości domyślne.Najlepiej można ekstrapolacji danych.Jeśli nie, zaleca się nie NULL i wartości domyślne.
W poniższym przykładzie jak FlushData jest zaimplementowana w RUpdateRowset klasy w UpdatePV próbki (patrz Rowset.h w kodzie przykładowym):
///////////////////////////////////////////////////////////////////////////
// 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
Dla dostawcy, aby obsłużyć zmiany najpierw upewnij się, że w magazynie danych (na przykład pliku tekstowego lub pliku wideo) ma urządzenia umożliwiające dokonywanie zmian na nim.Jeśli nie, należy utworzyć ten kod oddzielnie z projektu dostawcy.
Obsługa danych NULL
Jest możliwe, że użytkownik końcowy będzie wysyłać NULL danych.Gdy piszesz NULL wartości do pól w źródle danych, mogą być potencjalne problemy.Wyobraźmy sobie aplikację prowadzonej w kolejności, która akceptuje wartości dla miasta i kodu pocztowego; obie wartości, ale nie ani mogłaby zaakceptować, ponieważ w takim przypadku dostawy byłoby niemożliwe.Należy zatem ograniczyć niektórych kombinacji NULL wartości w polach, które są odpowiednie dla danej aplikacji.
Jako deweloper dostawcy należy wziąć pod uwagę, jak będą przechowywać te dane, jak będzie odczytywanie danych z magazynu danych i jak można określić, że użytkownikowi.W szczególności należy rozważyć, jaki zmienić stan danych z wierszy danych w źródle danych (na przykład DataStatus = NULL).Zdecydować, co wartość, aby powrócić, gdy klient uzyskuje dostęp do pola zawierające NULL wartości.
Spójrz na kod w UpdatePV próbki; ilustruje, jak dostawca może obsłużyć NULL danych.W UpdatePV, dostawca przechowuje NULL dane, wpisując ciąg znaków "NULL" w magazynie danych.Kiedy odczytuje NULL przechowywania danych w danych, widzi, że ciąg znaków, a następnie opróżnia bufor, tworzenie NULL ciąg znaków.Ma również zastępująca IRowsetImpl::GetDBStatus , w którym zwraca DBSTATUS_S_ISNULL , jeśli wartość danych jest pusta.
Znakowanie pustych kolumn
Jeśli także zaimplementować schemat wierszy (zobacz IDBSchemaRowsetImpl), implementacji należy określić w DBSCHEMA_COLUMNS wierszy (zwykle oznaczony w dostawcy przez cxxxSchemaColSchemaRowset) czy kolumny jest wartość NULL.
Należy również określić, że zawierają wszystkie pustych kolumn DBCOLUMNFLAGS_ISNULLABLE wartość w wersji GetColumnInfo.
W implementacji szablonów OLE DB Jeśli nie do oznaczenia kolumn jako wartość NULL, dostawca zakłada, że one musi zawierać wartość i nie zezwoli na konsumenta ją wysłać wartości null.
W poniższym przykładzie jak CommonGetColInfo funkcja jest zaimplementowana w CUpdateCommand (patrz UpProvRS.cpp) w UpdatePV.Uwaga jak kolumny mają to DBCOLUMNFLAGS_ISNULLABLE dla pustych kolumn.
/////////////////////////////////////////////////////////////////////////////
// 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
Tak jak w NULL danych, mają obowiązek zajmowania się zmiana wartości domyślne.
Domyślne ustawienie FlushData i Execute jest przywrócenie S_OK.Dlatego, jeśli ta funkcja nie zastępują, zmiany będą widoczne powiodła się (S_OK zostaną zwrócone), ale nie zostaną one przekazane do magazynu danych.
W UpdatePV próbki (w Rowset.h), SetDBStatus metody obsługi 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;
}
Kolumny flag
Jeśli wartości domyślne są obsługiwane na kolumn, należy ustawić przy użyciu metadanych w <</c1> klasy.> SchemaRowsetklasy dostawcyUstaw m_bColumnHasDefault = VARIANT_TRUE.
Istnieje również obowiązek ustawić flagi kolumny, które są określone za pomocą DBCOLUMNFLAGS typ wyliczeniowy.Flagi kolumny opisują właściwości kolumny.
Na przykład w CUpdateSessionColSchemaRowset klasy w UpdatePV (Session.h), pierwsza kolumna skonfigurowano w ten 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 wartość domyślną 0, to być zapisywalny, oraz że wszystkie dane w kolumnie mają taką samą długość.Jeśli chcesz, aby dane w kolumnie o zmiennej długości, czy nie ustawić tę flagę.