ADODC retrieve indivisuals values into EditBox - c++

Is there a method to retrieve individual values from ADODC into an edit box
I tried the following way :-
m_edit1=m_adodc1.GetRecordset().GetField().GetItem("table1_names");
but got an error "binary '=' no conversion available .....
as I remember

You can retrieve the value of a field with the Value property, you then need to call the SetWindowText() member function pof the edit control, like:
m_edit1.SetWindowText(m_adodc1.GetRecordset().GetField().GetItem("table1_names").Value());
EDIT: the value needs to be converted using the _bstr_t class:
LPTSTR lpValue = (LPTSTR)(_bstr_t) m_adodc1.GetRecordset().GetField().GetItem("table1_names").Value();
m_edit1.SetWindowText(lpValue);

ADODC or nothing ;)
however check the follwing :-
i made it ,,, partially !!
to inform you about my mdb file :-
its name (inventory.mdb)
it has one table (Stocks)
the fields are (StockName,StockID,...)
now i can extract values from only the "StockName" which its string values
by the following code in button click :-
m_ado.SetRecordSource ("SELECT * FROM Stocks");
m_ado.Refresh ();
C_Recordset m_Record = m_ado.GetRecordset ();// this line can be omitted !
COleVariant var1;
var1.vt = VT_I2;
var1.iVal = 1;
COleVariant value = m_ado.GetRecordset().GetFields().GetItem(var1).GetValue ();
m_edit = value.bstrVal;
UpdateData (FALSE);
how to extract the other values such as "StockID" which its integer or any other fields ?

Related

How to set a NULL column value through an ADO query connected to a MySQL ODBC source in C++?

Given a given MySQL table
DESC tabl_foo;
--------------------------------
Field Type Null Key Default Extra
-----------------------------------------------------------------
fooId varchar(15) NO PRI NULL
FooDate date YES MUL NULL
I need to update FooDate for a given row. So I tried this code :
query->SQL->Add("UPDATE tbl_foo SET FooDate = :FooDate WHERE fooId = :id"):
// both arguments are AnsiString
query->Parameters->FindParam("id")->Value = FoodId;
query->Parameters->FindParam("FooDate")->Value = FooDate;
query->ExecSQL();
However, this fails
Incorrect date value: "" for folumn FooDate at row 1
It turns out that FooDate may be an empty string (or { data:NULL }), and MySQL does not like that. So, I tried setting Null() :
if (FooDate.IsEmpty())
query->Parameters->FindParam("FooDate")->Value = Null();
else
query->Parameters->FindParam("FooDate")->Value = FooDate;
But then I get this error :
Parameter object is improperly defined. Inconsistent or incomplete information was provided.
What should be the value to set for MySQL NULL?
To make the update the system needs to know the types of the parameters. It can deduce the type from the variant you apply, and MySQL can do certain conversions for you, but it's always best to specify the data type.
So what you would need is something like:
if((pParam=query->Parameters->FindParam("FooDate"))!=NULL)
{
pParam->DataType=ftDate;
if(FooDate.IsEmpty())
{
pParam->Value=Null();
}
else
{
pParam->Value=FooDate;
}
}
You may want to look at keeping FooDate as a TDateTime, but you will need to detect NULL dates properly.

Show/Add an Item from/to Some ComboBoxs in C++ Builder XE8

I need your help to resolve my 2 problems.
I'm using 3 ComboBoxs (CB). When choosing an item from 1st CB, then an item of 2nd & 3rd CB will automatically come up .
On Delphi, all settings are work so well via "Text" property.
1st CB contains:
Google
Yahoo
2nd CB contains:
8.8.8.8
9.9.9.9
3rd CB contains:
8888
9999
Below is my code on Delphi :
procedure TForm.1stCBChange(Sender: TObject);
begin
if 1stCB.Text = 'Google' then begin
2ndCB.Text := '8.8.8.8';
3rdCB.Text := '8888';
end else
if 1stCB.Text = 'Yahoo' then begin
2ndCB.Text := '9.9.9.9';
3rdCB.Text := '9999';
end;
end;
The problem is that on C++ Builder XE8, it's not work properly via "Text" property. On the other words, the 2nd & 3rd CB's item is not shown via "Text" property.
void __fastcall TForm::1stCBChange(TObject *Sender)
{
if (1stCBChange->Text == "Google")
{
2ndCB->Text = "8.8.8.8";
3rdCB->Text = "8888";
}
else
if (1stCBChange->Text == "Yahoo")
{
2ndCB->Text = "9.9.9.9";
3rdCB->Text = "9999";
}
}
So to make it works, I have to use "ItemIndex" property.
void __fastcall TForm::1stCBChange(TObject *Sender)
{
if (1stCBChange->ItemIndex == 0)
{
2ndCB->ItemIndex = IntToStr(0);
// or 2ndCB->ItemIndex = 0;
3rdCB->ItemIndex = IntToStr(0);
}
else
if (1stCBChange->ItemIndex == 1)
{
2ndCB->ItemIndex = IntToStr(1);
3rdCB->ItemIndex = IntToStr(1);
}
}
I've tried some ways, but not works, e.g. 2ndCB->Items->Objects[1] = (TObject*) new String("8.8.8.8"); or 2ndCB->Items->AddObject("8.8.8.8",2ndCB); so on and on...
So my question: how to make it works on C++ Builder XE8 via "Text" option really like on Delphi ?
Thank you very much for your help.
About your first problem: you probably don't have the same Style property on the Combobox in Delphi and C++Builder. Setting it to csDropDown allows you to specify the Text, but csDropDownList doesn't.
Now, ItemIndex is an integer property, so the call to IntToStr is not needed. It shouldn't even compile, as you can't assign text to an integer variable, and in XE5 at least it doesn't. If it works later it must be because there is an operator int() or similar for UnicodeString, although I didn't find it on the latest documentation.
About your second issue: Add() adds the passed string to the end of the list. If you want to insert the string in a specific position, then you need to call Insert() which asks you for the position to insert it, in your case it would be 0. This, of course, assumes the StringList is not sorted.
Here is the Documentation for TStrings: http://docwiki.embarcadero.com/Libraries/XE5/en/System.Classes.TStrings

How to convert UnicodeString to BSTR?

I'm having a problem when calling OpenDatabase function (DAO). It's prototipe is:
virtual HRESULT STDMETHODCALLTYPE OpenDatabase(BSTR Name/*[in]*/, VARIANT Options/*[in,opt]*/,
VARIANT ReadOnly/*[in,opt]*/,
VARIANT Connect/*[in,opt]*/,
Dao_tlb::Database** ppDb/*[out,retval]*/) = 0; // [-1]
So, when I do this:
if(OpenDialog1->Execute() != true) return;
The selected filename is saved in OpenDialog1->FileName. Then I call the function above:
pDatabasePtr = pDBEngine->OpenDatabase(WideString(OpenDialog1->FileName).c_bstr(), myOpts, myRead, myCon);
and this works! But, the problem is when I try to set the filename to something else:
OpenDialog1->FileName = ParamStr(1); // OpenDatabase don't work in runtime - file not recognised!
or even set the filename inside a function:
pDatabasePtr = pDBEngine->OpenDatabase(WideString(L"SomeDB.mdb").c_bstr(), myOpts, myRead, myCon);
In both cases I get strange errors and never able to open a database. So, I probably convert UnicodeString/WideString to BSTR incorrectly.
So, why this function (OpenDatabase) works with
if(OpenDialog1->Execute() != true) return;
and does not work with
OpenDialog1->FileName = ParamStr(1);
How do I set the conversion correctly?
I found answer in here if anyone else needs it:
https://forums.embarcadero.com/thread.jspa?messageID=498776

Implementing bulk record fetching

At the start of my program, I need to read data from a MS Access database (.mdb) into a drop down control. This is done so that whenever the user types in that control, the application can auto-complete.
Anyway, the reading from database took forever so I thought I'd implement bulk row fetching.
This is the code I have:
CString sDsn;
CString sField;
sDsn.Format("ODBC;DRIVER={%s};DSN='';DBQ=%s",sDriver,sFile);
TRY
{
// Open the database
database.Open(NULL,false,false,sDsn);
// Allocate the rowset
CMultiRowset recset( &database );
// Build the SQL statement
SqlString = "SELECT NAME "
"FROM INFOTABLE";
// Set the rowset size. These many rows will be fetched in one bulk operation
recset.SetRowsetSize(25);
// Open the rowset
recset.Open(CRecordset::forwardOnly, SqlString, CRecordset::readOnly | CRecordset::useMultiRowFetch);
// Loop through each rowset
while( !recset.IsEOF() )
{
int rowsFetched = (int)recset.GetRowsFetched(); // This value is always 1 somehow
for( int rowCount = 1; rowCount <= rowsFetched; rowCount++ )
{
recset.SetRowsetCursorPosition(rowCount);
recset.GetFieldValue("NAME",sField);
m_nameDropDown.AddString(sField);
}
// Go to next rowset
recset.MoveNext();
}
// Close the database
database.Close();
}
CATCH(CDBException, e)
{
// If a database exception occured, show error msg
AfxMessageBox("Database error: "+e->m_strError);
}
END_CATCH;
MultiRowset.cpp looks like:
#include "stdafx.h"
#include "afxdb.h"
#include "MultiRowset.h"
// Constructor
CMultiRowset::CMultiRowset(CDatabase *pDB)
: CRecordset(pDB)
{
m_NameData = NULL;
m_NameDataLengths = NULL;
m_nFields = 1;
CRecordset::CRecordset(pDB);
}
void CMultiRowset::DoBulkFieldExchange(CFieldExchange *pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Text_Bulk(pFX, _T("[NAME]"), &m_NameData, &m_NameDataLengths, 30);
}
MultiRowset.h looks like:
#if !defined(__MULTIROWSET_H_AD12FD1F_0566_4cb2_AE11_057227A594B8__)
#define __MULTIROWSET_H_AD12FD1F_0566_4cb2_AE11_057227A594B8__
class CMultiRowset : public CRecordset
{
public:
// Field data members
LPSTR m_NameData;
// Pointers for the lengths of the field data
long* m_NameDataLengths;
// Constructor
CMultiRowset(CDatabase *);
// Methods
void DoBulkFieldExchange(CFieldExchange *);
};
#endif
And in my database, the INFOTABLE looks like:
NAME AGE
---- ---
Name1 Age1
Name2 Age2
.
.
.
.
All I need to do is only read the data from the database. Can someone please tell me what I'm doing wrong? My code right now behaves exactly like a normal fetch. There's no bulk fetching happening.
EDIT:
I just poked around in DBRFX.cpp and found out that RFX_Text_Bulk() initializes my passed m_NameData as new char[nRowsetSize * nMaxLength]!
This means m_NameData is only a character array! I need to fetch multiple names, so wouldn't I need a 2D character array? The strangest thing is, the same RFX_Text_Bulk() initializes my passed m_NDCDataLengths as new long[nRowsetSize]. Why in the world would a character array need an array of lengths?!
According to http://msdn.microsoft.com/en-us/library/77dcbckz.aspx#_core_how_crecordset_supports_bulk_row_fetching you have to open CRecordset with CRecordset::useMultiRowFetch flag before call SetRowsetSize:
To implement bulk row fetching, you must specify the
CRecordset::useMultiRowFetch option in the dwOptions parameter of the
Open member function. To change the setting for the rowset size, call
SetRowsetSize.
You almost got it right. To fetch the values,
I would change your
for( int rowCount = 1; rowCount <= rowsFetched; rowCount++ )
{
recset.SetRowsetCursorPosition(rowCount);
recset.GetFieldValue("NAME",sField);
m_nameDropDown.AddString(sField);
}
by something like this
for( int nPosInRowset = 0; nPosInRowset < rowsFetched; nPosInRowset++ )
{
//Check if value is null
if (*(recset.m_NameDataLengths + nPosInRowset) == SQL_NULL_DATA)
continue;
CString csComboString;
csComboString = (recset.m_NameData + (nPosInRowset * 30)); //Where 30 is the size specified in RFX_Text_Bulk
m_nameDropDown.AddString(csComboString);
}
EDIT: To fetch more than one row, remove the CRecordset::forwardOnly option
EDIT 2 : You can also keep CRecordset::forwardonly, but add the CRecordset::useExtendedFetch option
Just faced the same problem.
You should use in recset.Open() call for dwOptions parameter only CRecordset::useMultiRowFetch, and not CRecordset::readOnly | CRecordset::useMultiRowFetch.
Hope this helps someone...
EDIT:- After re-check here is the situation - when using bulk recordset and opening with CRecordset::forwardOnly and CRecordset::readOnly, you must also specify CRecordset::useExtendedFetch in dwOptions. For other types of scrolling, using CRecordset::readOnly | CRecordset::useMultiRowFetch is just fine.

Changing SQL Provider from SQLOLEDB.1 to SQLNCLI.1 causes app to fail when accessing data via stored procedure

I'm supporting a legacy app written in MFC/C++. The database for the app is in SQL Server 2000. We bolted on some new functionality recently and found that when we change the SQL Provider from SQLOLEDB.1 to SQLNCLI.1 some code that is trying to retrieve data from a table via a stored procedure fails.
The table in question is pretty straightforward and was created via the following script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UAllergenText](
[TableKey] [int] IDENTITY(1,1) NOT NULL,
[GroupKey] [int] NOT NULL,
[Description] [nvarchar](150) NOT NULL,
[LanguageEnum] [int] NOT NULL,
CONSTRAINT [PK_UAllergenText] PRIMARY KEY CLUSTERED
(
[TableKey] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[UAllergenText] WITH CHECK ADD CONSTRAINT
FK_UAllergenText_UBaseFoodGroupInfo] FOREIGN KEY([GroupKey])
REFERENCES [dbo].[UBaseFoodGroupInfo] ([GroupKey])
GO
ALTER TABLE [dbo].[UAllergenText] CHECK CONSTRAINT
FK_UAllergenText_UBaseFoodGroupInfo]
Bascially four columns, with TableKey being an identity column and everything else is populated via the following script:
INSERT INTO UAllergenText (GroupKey, Description, LanguageEnum)
VALUES (401, 'Egg', 1)
with a long list of other INSERT INTO's that follow the one above. Some of the rows inserted have special characters (like accent marks above letters) in their descriptions. I had originally thought that the inclusion of the special characters was part of the problem but if I completely clear out the table and then repopulate it with just the single INSERT INTO from above that has no special characters, it still fails.
So I moved on...
The data in this table is then accessed via the following code:
std::wstring wSPName = SP_GET_ALLERGEN_DESC;
_variant_t vtEmpty1 (DISP_E_PARAMNOTFOUND, VT_ERROR);
_variant_t vtEmpty2(DISP_E_PARAMNOTFOUND, VT_ERROR);
_CommandPtr pCmd = daxLayer::CDataAccess::GetSPCommand(pConn, wSPName);
pCmd->Parameters->Append(pCmd->CreateParameter("#intGroupKey", adInteger, adParamInput, 0, _variant_t((long)nGroupKey)));
pCmd->Parameters->Append(pCmd->CreateParameter("#intLangaugeEnum", adInteger, adParamInput, 0, _variant_t((int)language)));
_RecordsetPtr pRS = pCmd->Execute(&vtEmpty1, &vtEmpty2, adCmdStoredProc);
//std::wstring wSQL = L"select Description from UAllergenText WHERE GroupKey = 401 AND LanguageEnum = 1";
//_RecordsetPtr pRS = daxLayer::CRecordsetAccess::GetRecordsetPtr(pConn,wSQL);
if (pRS->GetRecordCount() > 0)
{
std::wstring wDescField = L"Description";
daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
}
else
{
nameString = "";
}
The daxLayer is a third party data access library the application is using, though we have the source to it (some of which will be seen below.) SP__GET_ALLERGEN_DESC is the stored proc used to get the data out of the table and it was created via this script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[spRET_AllergenDescription]
-- Add the parameters for the stored procedure here
#intGroupKey int,
#intLanguageEnum int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT Description FROM UAllergenText WHERE GroupKey = #intGroupKey AND LanguageEnum = #intLanguageEnum
END
When the SQL Provider is set to SQLNCLI.1, the app blows up at:
daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
from the above code snippet. So I stepped into GetField, which looks like the following:
void daxLayer::CRecordsetAccess::GetField(_RecordsetPtr pRS,
const std::wstring wstrFieldName, std::string& sValue, std::string sNullValue)
{
if (pRS == NULL)
{
assert(false);
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Missing recordset pointer."))
}
else
{
try
{
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
if ((tv.vt == VT_EMPTY) || (tv.vt == VT_NULL))
{
sValue = sNullValue;
}
else if (tv.vt != VT_BSTR)
{
// The type in the database is wrong.
assert(false);
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Field type is not string"))
}
else
{
_bstr_t bStr = tv ;//static_cast<_bstr_t>(pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value);
sValue = bStr;
}
}
catch( _com_error &e )
{
RETHROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string"), e.Description())
}
catch(...)
{
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Unknown error"))
}
}
}
The culprit here is:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
Stepping into Fields->GetItem brings us to:
GetItem
inline FieldPtr Fields15::GetItem ( const _variant_t & Index ) {
struct Field * _result = 0;
HRESULT _hr = get_Item(Index, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return FieldPtr(_result, false);
}
Which then takes us to:
GetValue
inline _variant_t Field20::GetValue ( ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = get_Value(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _variant_t(_result, false);
}
If you look at _result while stepping through this at runtime, _result's BSTR value is correct, its value is "Egg" from the "Description" field of the table. Continuing to step through traces back through all the COM release calls, etc. When I finally get back to:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
And step past it to the next line, the contents of tv, which should be BSTR="Egg" are now:
tv BSTR = 0x077b0e1c "ᎀݸﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮ㨼㺛帛᠄"
When the GetField function tries to set its return value to the value in tv.BSTR
_bstr_t bStr = tv;
sValue = bStr;
it unsurprisingly chokes and dies.
So what happened to the value of BSTR and why does it only happen when the provider is set to SQLNCLI.1?
For the heck of it, I commented out using the stored procedure in the topmost code and just hard coded the same SQL SELECT statement that the stored procedure uses and found that it works just fine and the value returned is correct.
Also, it's possible for users to add rows to the table through the application. If the application creates a new row in that table and retrieves that row via stored procedure, it also works correctly unless you include a special character in the description in which case it correctly saves the row but blows up again in the exact same way as above upon retrieval of that row.
So to summarize, if I can, rows put into the table via the INSERT script ALWAYS blow up the app when they are accessed by stored procedure (regardless of whether they contain any special characters). Rows put into the table from within the application by the user at runtime are retrieved correctly via stored procedure UNLESS they contain a special character in the Description, at which point they blow up the app. If you access any of the rows in the table by using SQL from the code at runtime instead of the stored procedure it works whether there is a special character in the Description or not.
Any light that can be shed on this will be greatly appreciated, and I thank you in advance.
This line might be problematic:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
If I read it right, ->Value returns a _variant_t, which is a smart pointer. The smart pointer will release its variant when it goes out of scope, right after this line. However, tagVARIANT is not a smart pointer, so it won't increase the reference count when it is assigned to. So after this line, tv might point to a variant which has effectively been released.
What happens if you write the code like this?
_variant_t tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
Or alternatively, tell the smart pointer not to release its payload:
_tagVARIANT tv = pRS->Fields->GetItem(
_variant_t(wstrFieldName.c_str()))->Value.Detach();
It's been a long time since I coded in C++, and reading this post, I don't regret moving away!