5

Apparently, in VS2012, SQL_CUR_USE_ODBC is deprecated. [Update: it appears that the cursors library has been removed from VS2012 entirely].

MFC's CDatabase doesn't use it anymore (whereas it was the default for VS2010 and earlier versions of MFC), but instead uses SQL_CUR_USE_DRIVER.

Unfortunately, SQL_CUR_USE_DRIVER doesn't work properly with the Jet ODBC driver (we're interacting with an Access database). The driver initially claims to support positional operations (but not positional updates), but when an attempt is made to actually query the database, all concurrency models fail until the MFC library drops down to read-only interaction with the database (which is not going to fly for us).

Questions

  • Is this MS's latest attempt to force devs to move away from Jet based datasources and migrate to SQL Express (or the like)?
  • Is there another modality that we should be using to interact with our Access databases through VS 2012 versions of MFC/ODBC?(1)

See also: http://social.msdn.microsoft.com/Forums/kk/vcmfcatl/thread/acd84294-c2b5-4016-b4d9-8953f337f30c


Update: Looking at the various options, it seems that the cursor library has been removed from VS2012's ODBC library. Combined with the fact that Jet doesn't correctly support positional updates(2), it makes "snapshot" mode unusable. It does appear to support "dynaset" as long as the underlying tables have a primary key. Unkeyed tables are incompatible with "dynaset" mode(3). So - I can stick with VS 2010, or I can change my tables to include an autonumber or something similar in order to ensure a pkey is available so I can use dynaset mode for the recordsets.


(1) e.g. should we be using a different open type for CRecordset? We currently use CRecordset::snapshot. But I've never really understood the various modes snapshot, dynamic, dynaset. A quick set of "try each" has failed to get a working updatable interface to our access database...
(2) it claims to when queried initially, but then returns errors for all concurrency modes that it previously claimed to support
(3) "dynamic" is also out, since Jet doesn't support it at all (from what I can tell from my tests).

Mordachai
  • 9,412
  • 6
  • 60
  • 112
  • MS gives very mixed messages about ODBC and Jet. DAO is deprecated, but Jet remains their ODBC driver engine for Access, and I've not stumbled upon anything concrete that indicates that they've deprecated Access as a product, or that they're removing support for Access from ODBC. They indicated that ODBC was going to be retired back around XP, but later reneged, with a new manager indicating that ODBC was to continue to be an important plank in their database connectivity technologies. So.. depending on what you read, sometimes it seems to be dying, other times seems to be in a Renaissance – Mordachai Oct 04 '12 at 13:04
  • 1
    Feeling your pain today after upgrading some legacy ODBC SQL code from VS 2010 to VS 2015. Microsoft's response to this bug is [laughably inadequate](https://connect.microsoft.com/VisualStudio/feedback/details/760374/cdatabase-usecursorlib-broken-in-mfc11); they tell us to "ask in the SQL forums". That's from an actual MS libraries developer. As if we needed more reason to migrate away from MFC. – System.Cats.Lol Jul 20 '16 at 15:54

3 Answers3

4

I found a solution that appears to work. I overrode OpenEx the exact same way VS 2012 has it because we need that to call the child version of AllocConnect since it is not virtual in the parent. I also overrode AllocConnect as mentioned. In the derived version of CDatabase, try the following code:

MyCDatabase.h

BOOL OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions = 0);
void AllocConnect(DWORD dwOptions);

MyCDatabase.cpp

BOOL MyCDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions)
{
ENSURE_VALID(this);
ENSURE_ARG(lpszConnectString == NULL || AfxIsValidString(lpszConnectString));
ENSURE_ARG(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog));

// Exclusive access not supported.
ASSERT(!(dwOptions & openExclusive));

m_bUpdatable = !(dwOptions & openReadOnly);

TRY
{
    m_strConnect = lpszConnectString;

    DATA_BLOB connectBlob;
    connectBlob.pbData = (BYTE *)(LPCTSTR)m_strConnect;
    connectBlob.cbData = (DWORD)(AtlStrLen(m_strConnect) + 1) * sizeof(TCHAR);
    if (CryptProtectData(&connectBlob, NULL, NULL, NULL, NULL, 0, &m_blobConnect))
    {
        SecureZeroMemory((BYTE *)(LPCTSTR)m_strConnect, m_strConnect.GetLength() * sizeof(TCHAR));
        m_strConnect.Empty();
    }

    // Allocate the HDBC and make connection
    AllocConnect(dwOptions);
    if(!CDatabase::Connect(dwOptions))
        return FALSE;

    // Verify support for required functionality and cache info
    VerifyConnect();
    GetConnectInfo();
}
CATCH_ALL(e)
{
    Free();
    THROW_LAST();
}
END_CATCH_ALL

return TRUE;
}

void MyCDatabase::AllocConnect(DWORD dwOptions)
{
CDatabase::AllocConnect(dwOptions);

dwOptions = dwOptions | CDatabase::useCursorLib;

// Turn on cursor lib support
if (dwOptions & useCursorLib)
{
    ::SQLSetConnectAttr(m_hdbc, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_ODBC, 0);
    // With cursor library added records immediately in result set
    m_bIncRecordCountOnAdd = TRUE;
}
}

Please note that you do not want to pass in useCursorLab to OpenEx at first, you need to override it in the hacked version of AllocConnect.

Also note that this is just a hack but it appears to work. Please test all your code to be sure it works as expected but so far it works OK for me.

saegeoff
  • 551
  • 5
  • 12
  • I honestly think this was a bug in MFC. The ODBC_CURSORS enum shouldn't be used with the SQL_CUR_USE_DRIVER enum. The code above effectively fixes that problem but it would be nice for Microsoft to fix this even though it is for a legacy library. There are tons of old programs out there that need this library for various reasons. – saegeoff Feb 21 '13 at 22:37
  • Interesting.... so you don't get a Cursor library failed to load error? This is against ACCESS, or SQL Server? – Mordachai Feb 22 '13 at 20:39
  • We are using this for an ODBC connection that can connect to SQL Server or Oracle. It appears to work flawlessly. So far we've had good luck. We just migrated to 2012 a few days ago and this has worked so far. I hope it helps you! – saegeoff Feb 23 '13 at 02:55
2

If anyone else runs into this issue, here's what seems to be the answer:

For ODBC to an Access database, connect using CDatabase mydb; mydb.OpenEx(.., 0), so that you ask the system not to load the cursor library.

Then for the recordsets, use dynaset CMyRecordset myrs; myrs.Open(CRecordset::dynaset, ...).

Finally, you must make sure that your tables have a primary key in order to use dynasets (keysets).

Mordachai
  • 9,412
  • 6
  • 60
  • 112
0

Derive CDatabase and override OpenEx. In your derived class CMyDatabase, replace the call to AllocConnect to MyAllocConnect. Obviously, your MyAllocConnect function should call SQLSetConnectOption with the desired parameter:

// Turn on cursor lib support
if (dwOptions & useCursorLib)
{
    AFX_SQL_SYNC(::SQLSetConnectOption(m_hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC));
    // With cursor library added records immediately in result set
    m_bIncRecordCountOnAdd = TRUE;
}

Then use your CMyDatabase class instead of CDatabase for all your queries.

l33t
  • 18,692
  • 16
  • 103
  • 180
  • 1
    Tried that - fails "Cursor Library failed to Load." MS appears to have actually removed the cursor library. It might be possible to restore it (pull it from VS 2010 - link against older ODBC), but with that supplied with VS 2012, it's a no-go. – Mordachai Oct 04 '12 at 13:01