I try to receive the numer of records for a Select statement and the record set at once.
The Recordset Object offers the RecordCount Property for this issue.
It works just fine using a static, server-side cursor, but if i view the events in SQL Server Profiler, I realize, that it seems to fetch every row of the whole recordset, just to count the rows.
On the other hand, I can do a MoveLast on the recordset and the Bookmark contains the index of the last row (== Recordcount).
I do not want to use the Bookmark instead of the RecordCount and wonder if someone could explain this behaviour.
If anyone is interessted, I created a small code sample to reproduce it:
::CoInitialize(NULL);
ADODB::_ConnectionPtr pConn;
HRESULT hr;
hr = pConn.CreateInstance(__uuidof(ADODB::Connection));
pConn->CursorLocation = ADODB::adUseServer;
pConn->ConnectionTimeout = 0;
pConn->Provider = "SQLOLEDB";
pConn->Open(bstr_t("Provider=sqloledb;Data Source=s11;Initial Catalog=...;Application Name=DBTEST"), "", "", ADODB::adConnectUnspecified);
// Create Command Object
_variant_t vtRecordsAffected;
ADODB::_CommandPtr cmd;
hr = cmd.CreateInstance(__uuidof(ADODB::Command));
cmd->ActiveConnection = pConn;
cmd->CommandTimeout = 0;
// Create a test table
cmd->CommandText = _bstr_t("create table #mytestingtab (iIdentity INT)");
cmd->Execute(&vtRecordsAffected, NULL, ADODB::adCmdText);
// Populate
cmd->CommandText = _bstr_t(
"DECLARE #iNr INT\r\n"
"SET #iNr = 0\r\n"
"WHILE #iNr < 10000\r\n"
"BEGIN\r\n"
" INSERT INTO #mytestingtab (iIdentity) VALUES (#iNr)\r\n"
" SET #iNr = #iNr + 1\r\n"
"END\r\n"
);
cmd->Execute(&vtRecordsAffected, NULL, ADODB::adCmdText);
// Create a Recordset Object
_variant_t vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
ADODB::_RecordsetPtr Recordset;
hr = Recordset.CreateInstance(__uuidof(ADODB::Recordset));
Recordset->CursorLocation = ADODB::adUseServer;
cmd->CommandText = _bstr_t(
"SELECT * FROM #mytestingtab"
);
Recordset->PutRefSource(cmd);
Recordset->Open(vtEmpty, vtEmpty, ADODB::adOpenStatic, ADODB::adLockReadOnly, ADODB::adCmdText);
// Move to the Last Row
Recordset->MoveLast();
_variant_t bookmark = Recordset->Bookmark;
// Recordcount
long tmp = Recordset->RecordCount;
Recordset->Close();
pConn->Close();
::CoUninitialize();
Is there a way to use the Recordset Property, without transferring all rows to the client??
Related
MSI has table - 'FeatureComponents' with two columns: 'Feature_' and 'Component_'. What I'm trying to do is to change all values in 'Feature_' column at once.
IntPtr hDb = IntPtr.Zero;
int res = MsiInvoke.MsiOpenDatabase(PathToMsi, MsiInvoke.MSIDBOPEN_TRANSACT, out hDb);
string FFF = "SELECT `Feature_` FROM `FeatureComponents`"; <- sql string
IntPtr hView2 = IntPtr.Zero;
res = MsiInvoke.MsiDatabaseOpenView(hDb, FFF, out hView2);
res = MsiInvoke.MsiViewExecute(hView2, IntPtr.Zero);
IntPtr hRec2 = IntPtr.Zero;
res = MsiInvoke.MsiViewFetch(hView2, out hRec2);
res = MsiInvoke.MsiRecordSetString(hRec2, 1, "DUMMY");
res = MsiInvoke.MsiViewModify(hView2, 4, hRec2);
res = MsiInvoke.MsiViewClose(hView2);
res = MsiInvoke.MsiDatabaseCommit(hDb);
However, this only changes first entry in table. So, I'm wondering, how to iterate over all entries in table and change all column values? As right now, I can only do this to one entry and have no idea how to apply this to all entries.
Basically you just turn that fetching code into a loop, and you keep calling MsiViewFetch until you get the ERROR_NO_MORE_ITEMS result, each call returning the next record until you get that error. Simple old example in C++ but the principle is the same:
while ( (errorI = MsiViewFetch (hViewSELECT, &hRecord)) != ERROR_NO_MORE_ITEMS)
nBuffer = (DWORD)256;
MsiRecordGetString(hRecord, 1, svPropname, &nBuffer);
nBuffer = (DWORD)256;
MsiRecordGetString(hRecord, 2, svPropvalue, nBuffer);
}
I am trying to insert a file into MS Access database, into a field of OLE Object type. I am using C++ and ADO.
Currently I get error Invalid pointer error.
I think that my problem is mishandling variants since this is the first time I use them. I am learning from this code example but have problem understanding how to insert file from disk into variant.
They read it from database, and copied it into new record so the part where I read file from disk and then insert it into variant is missing.
I am firing off my code in GUI when menu item is selected. Database has one table named test with fields ID which is primary key and field which is of OLE Object type.
After searching online I have found nothing that can help me.
Here is smallest code snippet possible that illustrates the problem ( error checking is minimal):
wchar_t *bstrConnect = L"Provider=Microsoft.ACE.OLEDB.12.0; \
Data Source = C:\\Users\\Smiljkovic85\\Desktop\\OLE.accdb";
try
{
HRESULT hr = CoInitialize(NULL);
// connection
ADODB::_ConnectionPtr pConn(L"ADODB.Connection");
// recordset
ADODB::_RecordsetPtr pRS(L"ADODB.Recordset");
// connect to DB
hr = pConn->Open(bstrConnect, L"admin", L"", ADODB::adConnectUnspecified);
// open file
std::ifstream in(L"C:\\Users\\Smiljkovic85\\Desktop\\file.pdf",
std::ios::ate | std::ios::binary);
// get file size
int fileSize = in.tellg();
// here I tried to adapt code from the example linked above
pRS->Open(L"test", _variant_t((IDispatch*)pConn, true),
ADODB::adOpenKeyset, ADODB::adLockOptimistic, ADODB::adCmdTable);
// add new record
pRS->AddNew();
// copy pasted
_variant_t varChunk;
SAFEARRAY FAR *psa;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
// modify to our file size
rgsabound[0].cElements = fileSize;
psa = SafeArrayCreate(VT_UI1, 1, rgsabound);
//=================== try to add file into variant
char *chData = (char *)psa->pvData;
chData = new char[fileSize];
in.read(chData, fileSize);
/* ============= I have even tried the following :
char *chData = new char[fileSize];
in.read(chData, fileSize);
BYTE* pData;
SafeArrayAccessData(psa, (void **)&pData);
memcpy(pData, chData, fileSize);
SafeArrayUnaccessData(psa);
===============*/
//=================================================
// Assign the Safe array to a variant.
varChunk.vt = VT_ARRAY | VT_UI1;
varChunk.parray = psa;
pRS->Fields->GetItem(L"field")->AppendChunk(varChunk);
// add this record into DB
pRS->Update();
// cleanup
delete[] chData;
in.close();
pRS->Close();
pConn->Close();
CoUninitialize();
}
catch (_com_error e)
{
MessageBox(hWnd, (LPWSTR)e.Description(), L"", 0);
}
Can you help me to modify this code snippet so I can insert file into variant?
EDIT:
I have searched here for help and two posts that gave me an idea. Still none of my solutions work. You can see them in the above code snippet, in the comments.
What I get now, is the following error: a problem occurred while microsoft access was communicating with the ole server or activex control in MS Access. I have searched online for solution but had no luck, every link claims it has to do with access and not with the code.
Please help...
Since you are already using ADODB.Connection and ADODB.Recordset objects you should be able to use a binary ADODB.Stream object to manipulate the file contents with
.LoadFromFile to fill the Stream with the file contents, and
.Read to pull it back out of the Stream and store it in the database field.
Unfortunately I cannot offer a C++ example, but in VBA the code would be:
Dim con As ADODB.Connection, rst As ADODB.Recordset, strm As ADODB.Stream
Set con = New ADODB.Connection
con.Open _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=C:\Users\Public\Database1.accdb"
Set rst = New ADODB.Recordset
rst.Open "test", con, adOpenKeyset, adLockOptimistic, adCmdTable
Set strm = New ADODB.Stream
strm.Type = adTypeBinary
strm.Open
strm.LoadFromFile "C:\Users\Gord\Desktop\test.pdf"
rst.AddNew
strm.Position = 0
rst.Fields("FileData").Value = strm.Read
rst.Update
rst.Close
Set rst = Nothing
con.Close
Set con = Nothing
strm.Close
Set strm = Nothing
I'm trying to get a recordset from a stored procedure using ADODB.
Stored procedures get executeed successfully (Doing everything written in the SP), but the recordset count is "-1".
Here is what I'm doing (stored procedure has no parameters):
hr = ptrCom.CreateInstance(__uuidof(Command));
ptrCom->ActiveConnection = _connection;
ptrCom->CommandType = adCmdStoredProc;
ptrCom->CommandText = _bstr_t(_T("get_all_users"));
_variant_t vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
ADODB::_RecordsetPtr record_set;
HRESULT normal_hr = ptrCom->raw_Execute(&vtEmpty, &vtEmpty, adCmdStoredProc, &record_set);
int cnt = record_set->RecordCount; // PROBLEM: cnt == -1 :-(
Can anyone point out what am I doing wrong?
thanks!
Have you tried just calling Execute?
ptrCom->Execute(NULL, NULL, ADODB::adCmdStoredProc);
returns _RecordSetPtr.
You might also want to try:
after you set the connection.
ptrCom->ActiveConnection->PutCursorLocation(ADODB::adUseClient);
In my C++ code, when I try to run the ADO command to insert rows into a table, it only inserts a certain number of rows. The same command works well when using System.Data.SqlClient in .NET.
Sql profiler shows the same textdata when using ADO or a sqlclient. Below is my insert commandtext, I'm not sure what else to do to uncover the issue here.
Any help on this is appreciated.
Command text :
declare #i int set #i = 1 while (#i < 255)
begin
insert into table1 (name,type, order, state) values (#i, N'type',0,0)
set #i = #i +1
end
The above command ends at 153 rows. Is this dependent on table size ?
If I send the command execute twice, consecutively one ranging from #i 0 to 150 and another from 150 to 255, all rows are inserted fine.
Am I'm hitting a limit on ADO command execute ?
This is my connection string and the code I'm using to build the connection :
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ADODB::_ConnectionPtr m_Conn = NULL;
TCHAR connString[255];
_stprintf(connString,
_T("DRIVER=SQL Server;SERVER=np:(local)\\MyInstance;DATABASE=test;"));
HRESULT hr;
hr = m_Conn.CreateInstance(__uuidof(ADODB::Connection));
if (hr != S_OK) {
wprintf(_T("CreateInstance(__uuidof(ADODB::Connection)) failed. Error: %d"), hr);
return 0;
}
m_Conn->ConnectionTimeout = 1800;
m_Conn->CommandTimeout = 1800;
hr = m_Conn->Open((LPCTSTR)connString, _T(""), _T(""),
ADODB::adConnectUnspecified);
if (hr != S_OK) {
wprintf(_T("Open(%ws, '', '', ADODB::adConnectUnspecified) failed."),
connString);
return 0;
}
Thanks for your help.
I got it work. Adding 'SET NOCOUNT ON' allowed the insert to continue. Not sure what the limit is in ADO.
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!