FIX: Loss of Decimal Digits with MFC Database Classes (134679)
The information in this article applies to:
- The Microsoft Foundation Classes (MFC), when used with:
- Microsoft Visual C++ for Windows, 16-bit edition 1.5
- Microsoft Visual C++ for Windows, 16-bit edition 1.51
- Microsoft Visual C++ for Windows, 16-bit edition 1.52
- Microsoft Visual C++, 32-bit Editions 2.0
- Microsoft Visual C++, 32-bit Editions 2.1
- Microsoft Visual C++, 32-bit Editions 2.2
- Microsoft Visual C++, 32-bit Editions 4.0
- Microsoft Visual C++, 32-bit Editions 4.1
- Microsoft Visual C++, 32-bit Enterprise Edition 4.2
- Microsoft Visual C++, 32-bit Professional Edition 4.2
- Microsoft Visual C++, 32-bit Enterprise Edition 5.0
- Microsoft Visual C++, 32-bit Professional Edition 5.0
This article was previously published under Q134679 SYMPTOMS
When you add or update a record that includes a numeric or decimal field
(ODBC SQL data types SQL_DECIMAL and SQL_NUMERIC), all the digits to the
right of the decimal point are lost.
CAUSE
By default, ClassWizard or AppWizard map the SQL_DECIMAL and SQL_NUMERIC
SQL data types to the C data type SQL_C_CHAR. The MFC RFX_Text() routine
that is used for this data type (and also for normal character types)
always specifies 0 for the ibScale parameter when calling the ODBC API
function SQLSetParam() (as of Visual C++ 4.0, SQLBindParameter is the ODBC
API that is called). The ibScale parameter is used to specify the number of
digits to the right of the decimal point.
NOTE: As of Visual C++ version 4.2, a sixth parameter (nScale) has been
added to RFX_Text. This parameter is used to specify the scale for tranfers
of data that are explicitly marked as parameters by the programmer. For
outputColumn field types (that is, non-parameters), RFX_Text still
specifies a scale of 0 so truncation may occur.
WORKAROUND
Write a custom RFX function that specifies the correct scale value and use
it in place of RFX_Text() for those fields that have the problem. See the
"Building and Using a New RFX Function" section in this article for
details. One possible implementation based on the MFC 2.52 RFX_Text()
implementation is shown in the sample code section at the end of this
article.
STATUS
Microsoft has confirmed this to be a bug in the Microsoft products listed
at the beginning of this article. This problem was corrected in Visual C++
version 6.0 for Windows.
MORE INFORMATIONBuilding and Using a New RFX Function to Work Around Problem
The first step is to create a new RFX function. This function should
delegate to RFX_Text() for everything but the CFieldExchange::NameValue
case, the CFieldExchange::Value case (for fields that are bound to database
columns), and the CFieldExchange::BindParam case (for parameter fields --
not required for Visual C++ version 4.2 or later) of the CFieldExchange
object's m_nOperation member variable. You will also need to add an extra
parameter to the RFX function and pass the scale value there. Note that
this parameter already exists in Visual C++ version 4.2 or later. NOTE: To be ODBC 2.0 compliant, you can replace calls to SQLSetParam with
calls to SQLBindParameter. Visual C++ 4.x uses SQLBindParameter.
The RFX_ function should be structured like this:
void AFXAPI RFX_MyText(CFieldExchange* pFX, const char* szName,
CString& value, int nMaxLength,
int nColumnType, int nScale)
{
if (pFX->m_nOperation != CFieldExchange::NameValue &&
pFX->m_nOperation != CFieldExchange::Value &&
pFX->m_nOperation != CFieldExchange::BindParam)
{
RFX_Text(pFX, szName, value, nMaxLength, nColumnType);
return;
}
// initialize and check things
switch (pFX->m_nOperation)
{
case NameValue:
// handle NameValue case
case Value:
// handle Value case
// not required for Visual C++ 4.2 or greater
case BindParam:
// handle BindParam case
}
}
The NameValue case is brought along because it falls into the Value case.
In the Value case, you need to change the call to SQLSetParam to specify
the scale. Here is the relevant section of the Value case from the MFC 2.52
RFX_Text() function (slightly reformatted):
AFX_SQL_SYNC(::SQLSetParam(m_hstmt,
(unsigned short int)m_nParamFields,
(short int)nCType,
// Get actual SQL type -
// may differ from CType (e.g. DECIMAL)
(short int)GetColumnType(nField),
(unsigned long int)cbPrecision,
0,
pv,
plLength));
NOTE: The sixth parameter is 0. This is what you need to change. Change
it to what is passed in the nScale parameter added to the RFX function. You
will also be using the value passed in the nColumnType parameter to specify
the SQL data type (4th parameter of SQLSetParam()):
AFX_SQL_SYNC(::SQLSetParam(pFX->m_hstmt,
(unsigned short int)pFX->m_nParamFields,
SQL_C_CHAR,
(short int)nColumnType,
(unsigned long int)value.GetLength(),
(short int)nScale,
value.GetBuffer(0),
plLength));
value.ReleaseBuffer();
NOTE: Because the SQLSetParam() call is now in the context of an RFX
function, you must use the CFieldExchange pointer (pFX) to access the
CFieldExchange members m_hstmt and m_nParamFields. If you borrow the rest
of the NameValue and Value case code from the CFieldExchange::Default()
function (which you should), you need to be aware of this throughout the
code.
For the BindParam case, you can just copy the implementation of this case
from RFX_Text() and change the sixth parameter of the SQLSetParam() call
from 0 to the value passed in through the nScale parameter of your custom
RFX_ function (not required for Visual C++ version 4.2 or later).
To use the new RFX function, change the call to RFX_Text for the fields in
question in your recordset's DoFieldExchange() function to calls to the new
RFX function you just wrote. Be aware that you will now be specifying six
parameters in your RFX function call. This call should be placed outside of
Class Wizard's AFX_FIELD_MAP comment block.
Sample Code to Implement Workaround
/* Compile options needed: None
*/
// implementation based on VC++ 1.52 code
// prototype
void AFXAPI RFX_MyText(CFieldExchange* pFX, const char* szName,
CString& value, int nMaxLength, int nColumnType, int nScale);
// implementation
void AFXAPI RFX_MyText(CFieldExchange* pFX, const char* szName,
CString& value, int nMaxLength, int nColumnType, int nScale)
{
if (pFX->m_nOperation != CFieldExchange::NameValue &&
pFX->m_nOperation != CFieldExchange::Value &&
pFX->m_nOperation != CFieldExchange::BindParam)
{
RFX_Text(pFX, szName, value, nMaxLength, nColumnType);
return;
}
ASSERT(AfxIsValidAddress(pFX, sizeof(CFieldExchange)));
ASSERT(AfxIsValidString(szName));
ASSERT(AfxIsValidAddress(&value, sizeof(CString)));
RETCODE nRetCode;
UINT nField;
if (!pFX->IsFieldType(&nField))
return;
LONG* plLength = pFX->m_prs->GetFieldLength(pFX);
switch (pFX->m_nOperation)
{
case CFieldExchange::NameValue:
if (pFX->m_prs->IsFieldFlagDirty(nField,pFX->m_nFieldType))
{
*pFX->m_pstr += pFX->m_prs->m_pDatabase->QuoteName(szName);
*pFX->m_pstr += "=";
}
// Fall through
case CFieldExchange::Value:
if (pFX->m_prs->IsFieldFlagDirty(nField, pFX->m_nFieldType))
{
// If user marked column NULL, reflect this in length
if (pFX->m_prs->IsFieldFlagNull(nField, pFX->m_nFieldType))
*plLength = SQL_NULL_DATA;
else
*plLength = value.GetLength();
// If optimizing for bulk add, just set length.
if (!(pFX->m_prs->m_dwOptions & CRecordset::optimizeBulkAdd))
{
*pFX->m_pstr += "?";
*pFX->m_pstr += pFX->m_lpszSeparator;
pFX->m_nParamFields++;
AFX_SQL_SYNC(::SQLSetParam(pFX->m_hstmt,
(unsigned short int)pFX->m_nParamFields,
SQL_C_CHAR,
(short int)nColumnType,
(unsigned long int)value.GetLength(),
(short int)nScale,
value.GetBuffer(0),
plLength));
value.ReleaseBuffer();
if (nRetCode != SQL_SUCCESS)
pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);
}
}
return;
case CFieldExchange::BindParam:
*plLength = SQL_NTS;
// Preallocate to nMaxLength
value.GetBufferSetLength(nMaxLength);
AFX_SQL_SYNC(::SQLSetParam(pFX->m_hstmt,
(unsigned short int)nField,
(short int)SQL_C_CHAR,
(short int)nColumnType,
(unsigned long int)value.GetLength(),
nScale,
value.GetBuffer(0),
plLength));
value.ReleaseBuffer();
if (nRetCode != SQL_SUCCESS)
pFX->m_prs->ThrowDBException(nRetCode, pFX->m_hstmt);
return;
}
}
REFERENCES
ODBC 2.0 Programmer's Reference, Appendix D - Data Types
Modification Type: | Major | Last Reviewed: | 12/9/2003 |
---|
Keywords: | kbBug kbfix kbVC600fix KB134679 |
---|
|