Tworzenie aktualizowalnego dostawcy
Visual C++ 6.0 obsługiwane tylko dostawców tylko do odczytu.Program Visual C++ .NET obsługuje aktualizowalna dostawcy lub dostawców, które można aktualizować (zapis do) do przechowywania danych.W tym temacie omówiono sposób tworzenia dostawców mogą być aktualizowane przy użyciu szablonów OLE DB.
W tym temacie założono, jeśli zaczynasz z dostawcą wykonalne.Istnieją dwa kroki tworzenia aktualizowalna dostawcy.Najpierw należy zdecydować, jaki dostawca wprowadzi zmiany do magazynu danych; w szczególności czy zmiany muszą być wykonywane natychmiast albo odłożone do momentu wydania polecenia Aktualizuj.Sekcja "Dokonywanie dostawców można aktualizować" w tym artykule opisano zmiany i ustawienia, które należy wykonać w kodzie dostawcy.
Należy dalej, upewnij się, że dostawca zawiera wszystkie funkcje do obsługi coś, konsument może żądać jego.Jeśli konsument chce zaktualizować magazyn danych, dostawca musi zawierać kod, który pozostawałby danych do magazynu danych.Na przykład można użyć C Run-Time Library lub MFC do wykonywania takich operacji na źródło danych.Sekcji "piśmie do źródła danych" opisano sposób pisać w źródle danych, zajmują się NULL i wartości domyślne i ustawić flagi kolumna.
[!UWAGA]
UpdatePV jest przykładem mogą być aktualizowane dostawcy.UpdatePV jest taka sama, jako MyProv, ale z obsługą mogą być aktualizowane.
Dokonywanie dostawców mogą być aktualizowane
Kluczem do dostawcy mogą być aktualizowane jest zrozumienie, jakie operacje chcesz z dostawcą, aby wykonać na magazynie danych oraz sposób dostawcy do przeprowadzenia tych operacji.W szczególności, największym problemem jest, czy są aktualizacje do magazynu danych do zrobienia natychmiast lub odroczone (przetwarzany wsadowo) aż do chwili wydania polecenie aktualizacji.
Najpierw należy podjąć decyzję o dziedziczą z IRowsetChangeImpl lub IRowsetUpdateImpl w klasie zestaw wierszy.W zależności od tego, która z nich wybierzesz do wdrożenia, będzie miała wpływ funkcjonalność 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ć zmiany w magazynie danych, dopóki nie zostanie wywołana Aktualizacja, GetOriginalData, lub Cofnij.Jeśli aktualizacja obejmuje kilka zmian, są one wykonywane w trybie wsadowym (należy zauważyć, że tworzenie pakietów wsadowych zmian można dodać pamięć znaczne obciążenie).
Należy zauważyć, że IRowsetUpdateImpl wynika z IRowsetChangeImpl.Tak więc IRowsetUpdateImpl daje zmiana zdolności plus możliwość partii.
Do obsługi aktualizacji w dostawcy
W klasie wierszy dziedziczą z IRowsetChangeImpl lub IRowsetUpdateImpl.Klasy te przewidują odpowiednich interfejsów zmiana magazynu danych:
Dodawanie IRowsetChange
Dodaj IRowsetChangeImpl do łańcucha dziedziczenia za pomocą tego formularza:
IRowsetChangeImpl< rowset-name, storage-name >
Również dodać COM_INTERFACE_ENTRY(IRowsetChange) do BEGIN_COM_MAP sekcja w klasie zestaw wierszy.
Dodawanie IRowsetUpdate
Dodaj IRowsetUpdate do łańcucha dziedziczenia za pomocą tego formularza:
IRowsetUpdateImpl< rowset-name, storage>
[!UWAGA]
Należy usunąć IRowsetChangeImpl linii od swojej łańcucha dziedziczenia.Ten jeden wyjątek do wyżej wymienione dyrektywy muszą zawierać kod IRowsetChangeImpl.
Dodaj następującą wartość 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 na mapie zestaw właściwości (BEGIN_PROPSET_MAP... END_PROPSET_MAP):
W przypadku zastosowania
Dodać 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 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, zaglądając do 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 ich odczytu/zapisu, ale szablonów OLE DB może obsługiwać tylko jedną wartość.
W przypadku zastosowania IRowsetChangeImpl
W przypadku zastosowania IRowsetChangeImpl, należy ustawić następujące właściwości na dostawcę.Właściwości te są stosowane głównie do żądania interfejsów poprzez ICommandProperties::SetProperties.
DBPROP_IRowsetChange: To automatycznie ustawienie zestawy DBPROP_IRowsetChange.
DBPROP_UPDATABILITY: Maska bitów określająca obsługiwanych metod 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.
W przypadku zastosowania 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 wymienionych powyżej:
DBPROP_IRowsetUpdate.
DBPROP_OWNINSERT: Musi być VARIANT_TRUE i READ_ONLY.
DBPROP_OWNUPDATEDELETE: Musi być VARIANT_TRUE i READ_ONLY.
DBPROP_OTHERINSERT: Musi być VARIANT_TRUE i READ_ONLY.
DBPROP_OTHERUPDATEDELETE: Musi być VARIANT_TRUE i READ_ONLY.
DBPROP_REMOVEDELETED: Musi być VARIANT_TRUE i READ_ONLY.
DBPROP_MAXPENDINGROWS.
[!UWAGA]
Jeśli obsługi powiadomień, może być również pewne inne właściwości, jak również; zobacz sekcję o IRowsetNotifyCP dla tej listy.
Na przykład w jaki sposób ustawiane są właściwości, zobacz właściwość zestaw mapę CUpdateCommand (w Rowset.h) w UpdatePV.
Pisanie w źródle danych
Aby przeczytać ze źródła danych, należy wywołać Execute funkcji.Pisać w źródle danych, należy wywołać FlushData funkcji. (W sensie ogólnym opróżnić środków, aby zapisać zmiany wprowadzone do 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.Zwykle napisać jedno pole danych w czasie.
FlushData Metoda zapisuje dane w formacie, w którym był początkowo przechowywany.Jeśli ta funkcja nie jest zastępują, Twój dostawca będzie działał poprawnie, ale zmiany nie zostaną opróżnione do magazynu danych.
Kiedy należy opróżnić
Wywołanie szablony dostawca FlushData w każdym przypadku, gdy dane trzeba napisać do magazynu danych; to zwykle (ale nie zawsze) występuje w wyniku wywołania do następujących funkcji:
IRowsetChange::DeleteRows
IRowsetChange::SetData
IRowsetChange::InsertRows (jeśli ma nowych danych do wstawienia 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 każdej nadarzającej się 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 ulepszyć zabezpieczenia.A następnie wywołania dostawcy FlushData.
Jak wdrożyć FlushData
Aby zaimplementować FlushData, trzeba wziąć pod uwagę kilka problemów:
Upewnienie się, że magazyn danych może obsłużyć zmian.
Obsługa wartości NULL wartości.
Obsługa wartości domyślnych.
Aby zaimplementować własne FlushData metody, trzeba:
Przejdź do swojej klasy zestaw wierszy.
W zestawie wierszy klasy umieścić deklaracji:
HRESULT FlushData(HROW, HACCESSOR)
{
// Insert your implementation here and return an HRESULT.
}
- Implementacja zapewnia FlushData.
Dobre wykonanie FlushData przechowuje tylko wiersze i kolumny, które faktycznie zostały zaktualizowane.Można użyć HROW i HACCESSOR parametry, aby określić bieżący wiersz i kolumna są przechowywane na optymalizację.
Zazwyczaj największym wyzwaniem współpracuje z własnym Sklepie danych w trybie macierzystym.Jeśli to możliwe spróbuj:
Zachowaj metoda zapisywania do magazynu danych tak proste, jak to możliwe.
Obsługi wartości NULL wartości (opcjonalne, ale zaleca).
Uchwyt wartości domyślne (opcjonalne, ale zaleca).
Najlepszym rozwiązaniem jest ma rzeczywiste wartości określonej w magazynie danych dla wartości NULL i wartości domyślne.Najlepiej umożliwiają ekstrapolację tych danych.Jeśli nie, są poinformowani, aby nie dopuścić wartości NULL i wartości domyślne.
W poniższym przykładzie pokazano sposób 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ługiwanie zmian
Aby obsługiwać zmiany swojego dostawcy należy najpierw upewnij się, że Twój magazyn danych (na przykład pliku tekstowego lub pliku wideo) posiada udogodnienia, które pozwalają na dokonywanie zmian na nim.Jeśli tak nie jest, że kod należy utworzyć oddzielnie z projektu dostawcy.
Obsługa danych o wartości NULL
Istnieje możliwość, że użytkownik końcowy będzie wysyłać wartości NULL danych.Podczas wpisywania wartości NULL wartości do pola w źródle danych, mogą być potencjalne problemy.Wyobraź sobie aplikacji sporządzania zamówienia, która akceptuje wartości dla miasta i kod pocztowy; może to zaakceptować lub obie wartości, ale nie ani, ponieważ w takim przypadku dostawy byłoby niemożliwe.Dlatego trzeba ograniczyć niektóre kombinacje wartości NULL wartości pól, które mają sensu dla aplikacji.
Jako deweloper dostawcy należy wziąć pod uwagę, jak będą przechowywać te dane, jak będzie odczytywania danych z magazynu danych i jak można określić, że do użytkownika.W szczególności należy rozważyć, jak zmienić stan danych wierszy danych w źródle danych (na przykład DataStatus = wartości NULL).Zadecydować, jaka wartość ma być powrócić, gdy klient uzyskuje dostęp do pole zawierające wartości NULL wartość.
Przyjrzyj się kod w UpdatePV próbki; ilustruje, jak dostawca może obsłużyć wartości NULL danych.W UpdatePV, dostawca przechowuje wartości NULL danych, tworząc ciąg znaków "NULL" w magazynie danych.Kiedy odczytuje wartości NULL przechowywania danych w, widzi tego ciągu, a następnie opróżnia bufor, tworzenie wartości NULL ciąg znaków.Posiada również zastępująca IRowsetImpl::GetDBStatus w którym zwraca DBSTATUS_S_ISNULL Jeśli tę wartość danych jest pusta.
Znakowanie pustych kolumn
Jeśli także zaimplementować schemat wierszy (zobacz IDBSchemaRowsetImpl), implementacji należy określić w DBSCHEMA_COLUMNS zestaw wierszy (zwykle oznaczony w dostawcy przez CxxxSchemaColSchemaRowset) kolumna ma wartość null.
Należy również określić, że zawierają wszystkie kolumny dopuszczającej wartości null DBCOLUMNFLAGS_ISNULLABLE wartości w danej wersji programu GetColumnInfo.
W celu wykonania szablonów OLE DB zaznaczyć kolumny dopuszczającej wartości null, w przeciwnym przypadku dostawca zakłada, że musi zawierać wartość i nie pozwoli konsumenta do jej wysłania wartości null.
W poniższym przykładzie pokazano sposób, w jaki CommonGetColInfo funkcja jest realizowana w CUpdateCommand (patrz UpProvRS.cpp) w UpdatePV.Należy zwrócić uwagę, 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
Podobnie jak w przypadku wartości NULL danych, masz obowiązek radzić sobie ze zmianą wartości domyślne.
Domyślne ustawienie FlushData i Execute powinien zwrócić S_OK.W związku z tym, jeśli ta funkcja nie jest zastępują, to zmieni się powiodła się (S_OK zostaną zwrócone), ale nie będą przesyłane do magazynu danych.
W UpdatePV próbki (w Rowset.h), SetDBStatus metoda obsługuje następujące wartości domyślne:
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;
}
Kolumna flag
Jeśli wartości domyślne obsługuje na kolumn, należy ustawić go za pomocą metadanych w <klasy dostawcy>SchemaRowset klasy.Zestaw 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ą cechy charakterystyczne 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 domyślną wartość 0, aby być zapisywalny, oraz że wszystkie dane w kolumnie mają tę samą długość.Jeśli chcesz, aby dane w kolumnie o zmiennej długości, czy nie ustawiono tę flagę.