Related
I am trying to run an SQL stored procedure through ADO in C++. The procedure is called (for argument's sake) testProcedure and expects two parameters: #param1 and #param2. Here is a trimmed version of the code in the execution method:
m_mCommandParameters[_T("param1")] = _T("foo");
m_mCommandParameters[_T("param2")] = _T("bar");
pCommand.CreateInstance(__uuidof(Command));
pCommand->ActiveConnection = link;
pCommand->CommandType = adCmdStoredProc;
pCommand->CommandText = _T("testProcedure");
pCommand->PutPrepared(true);
pCommand->NamedParameters = true;
// Set up the variant to store the parameter values
VARIANT vParamValue;
vParamValue.vt = VT_BSTR;
CString paramCount; // Stores the count for use as parameter name
// Iterate through set parameters and apply them to command
map<CString,CString>::iterator mItr;
for(mItr = m_mCommandParameters.begin(); mItr != m_mCommandParameters.end(); mItr++) {
paramCount = mItr->first;
vParamValue.bstrVal = _bstr_t(mItr->second);
// Append the parameter
if (mItr->second.IsEmpty()) {
_variant_t vtNULL;
vtNULL.vt = VT_NULL;
pCommand->Parameters->Append(
pCommand->CreateParameter(_bstr_t(L"#"+paramCount),adVarChar,adParamInput,10,vtNULL)
);
} else {
pCommand->Parameters->Append(
pCommand->CreateParameter(_bstr_t(L"#"+paramCount),adVarWChar,adParamInput,
//commandParameters[i].GetLength()+1,
sizeof(vParamValue),
_bstr_t(vParamValue))
);
}
}
_variant_t vRecordsAffected;
pRecordSet = pCommand->Execute(&vRecordsAffected,NULL,adCmdStoredProc);
My understanding is that this should essentially execute the following:
testProcedure #param1 = 'foo', #param2 = 'bar'
If I open SQL management studio and run the above it works fine. But when I try and run the C++ I get the error:
database error IDispatch error #3092 80040e14 query : testProcedure;
[Microsoft][ODBC SQL Server Driver]Syntax error or access violation.
I only have SQL express so don't have SQL profiler; I usually use Express Profiler but for some reason it doesn't display any trace on stored procedured. So I am not sure how to start debugging this.
Thanks!
I have MS Access 2007 database with the following schema:
Main table Object< # Object_PK, ... >
Child table Electric_Consumption< $ Object_PK, # Electric_Consumption_PK, ... >
Child table Water_Consumption< $ Object_PK, # Water_Consumption_PK, ... >
Child table Educational_Object< $ Object_PK, # Educational_Object_PK, ... > which has child tables defined like this:
School< $ Educational_Object_PK, # School_PK, ... >
University< $ Educational_Object_PK, # University_PK, ... >
Here is the picture that should make things clearer:
I use ADO and C++ to insert data.
First I need to enter data for main table Object. I can successfully do that with INSERT query.
My problem is following:
After the above operation I need to insert Object's primary key into child tables, since it is their foreign key.
Allow me to describe exactly what I need so community can help me:
As I said, first I insert data into main table Object.
Then I need to insert data and Object's primary key into child tables.
Browsing through Internet I have found ##IDENTITY that might help me but I do not know if it works for my case.
To make things harder, this will be done in for loop ( the value of the Object_PK is the same in every INSERT and is equal to the value of the last inserted record for the Object ) , something like this:
for ( //... )
L"INSERT INTO Electric_Consumption ( Object_PK, field1, field2 ... )
values ( Object_pk // should I use here ##IDENTITY ? );
Then the same thing should be repeated for tables Water_Consumption and Educational_Object.
After I finish this, I need to add data in the Educational_Object's child tables.
The same as above, only instead of Object_PK I need to add Educational_Object_PK.
Here is the pseudo-code to clarify things better:
L"INSERT INTO Object ( ... ) values ( ... ); //this is OK
for ( ... )
L" INSERT INTO Electric_Consumption ( Object_PK, ... )
values ( Object_PK, ... )"; // should I use ##IDENTITY here
// to get Object_PK ??
for ( ... )
L" INSERT INTO Water_Consumption ( Object_PK, ... )
values ( Object_PK, ... )"; // should I use ##IDENTITY here
// to get Object_PK ??
for ( ... )
L" INSERT INTO Educational_Object ( Object_PK, ... )
values ( Object_PK, ... )"; // should I use ##IDENTITY here
// to get Object_PK ??
for ( ... )
L" INSERT INTO School ( Educational_Object_PK, ... )
values ( Educational_Object_PK, ... )";// should I use ##IDENTITY here
// to get Educational_Object_PK ??
for ( ... )
L" INSERT INTO University ( Educational_Object_PK, ... )
values ( Educational_Object_PK, ... )";// should I use ##IDENTITY here
// to get Educational_Object_PK ??
Can you please tell me which SQL statement to use for this, and demonstrate how to use it by providing a small pseudo code?
I understand that my description of the problem might be confusing so if you need further clarification leave a comment and I will edit my post.
Thank you.
Best regards.
Yes, you want to use SELECT ##IDENTITY as a multiuser-safe way to retrieve the most recently-created AutoNumber (sometimes called "IDENTITY") value. The things to remember are:
You execute a SELECT ##IDENTITY query immediately after you perform the INSERT on the parent table.
You store the returned value in a Long Integer variable.
You use the variable to populate the Foreign Key values in the child table(s).
The following is VBA code, but you can treat it as pseudo-code:
Dim lngObject_PK As Long, lngEducational_Object_PK As Long
Set cmd = New ADODB.Command
cmd.ActiveConnection = con
cmd.CommandText = "INSERT INTO [Object] ([Description]) VALUES (?)"
cmd.Parameters.Append cmd.CreateParameter("?", adVarWChar, adParamInput, 255, "my new Object")
cmd.Execute
Set cmd = Nothing
Set rst = New ADODB.Recordset
rst.Open "SELECT ##IDENTITY", con, adOpenStatic, adLockOptimistic
lngObject_PK = rst(0).Value
rst.Close
Set rst = Nothing
Debug.Print "Object_PK of newly-created Object record: " & lngObject_PK
Set cmd = New ADODB.Command
cmd.ActiveConnection = con
cmd.CommandText = "INSERT INTO [Electric_Consumption] ([Object_PK],[Description]) VALUES (?,?)"
cmd.Parameters.Append cmd.CreateParameter("?", adInteger, adParamInput, , lngObject_PK)
cmd.Parameters.Append cmd.CreateParameter("?", adVarWChar, adParamInput, 255, "my new Electric_Consumption")
cmd.Execute
Set cmd = Nothing
Set cmd = New ADODB.Command
cmd.ActiveConnection = con
cmd.CommandText = "INSERT INTO [Educational_Object] ([Object_PK],[Description]) VALUES (?,?)"
cmd.Parameters.Append cmd.CreateParameter("?", adInteger, adParamInput, , lngObject_PK)
cmd.Parameters.Append cmd.CreateParameter("?", adVarWChar, adParamInput, 255, "my new Educational_Object")
cmd.Execute
Set cmd = Nothing
Set rst = New ADODB.Recordset
rst.Open "SELECT ##IDENTITY", con, adOpenStatic, adLockOptimistic
lngEducational_Object_PK = rst(0).Value
rst.Close
Set rst = Nothing
Debug.Print "Educational_Object_PK of newly-created Educational_Object record: " & lngEducational_Object_PK
Set cmd = New ADODB.Command
cmd.ActiveConnection = con
cmd.CommandText = "INSERT INTO [School] ([Educational_Object_PK],[Description]) VALUES (?,?)"
cmd.Parameters.Append cmd.CreateParameter("?", adInteger, adParamInput, , lngEducational_Object_PK)
cmd.Parameters.Append cmd.CreateParameter("?", adVarWChar, adParamInput, 255, "my new School")
cmd.Execute
Set cmd = Nothing
If the Object_PK is predictable, such as you are using an Autonumber field, you could first determine the next key by something like:
SELECT Max([Object_ID]+1) AS NewKey
FROM ObjectTable;
then use that for all of the other tables (or simply retrieve the MAX key value after storing the Object); How is the primary key defined?
#include <iostream>
#include <mysql++.h>
using namespace std;
int main() {
// Get database access parameters from command line
const char* db = "enet", *server = "192.168.1.108", *user = "root", *pass =
"123456";
// Connect to the sample database.
mysqlpp::Connection conn(false);
conn.set_option(new mysqlpp::MultiStatementsOption(true));
if (conn.connect(db, server, user, pass)) {
mysqlpp::Query query = conn.query();
query << "call CreateTable('test1', 'generic', 0, 1, 2, 3,4,5,6,7,8,9,10,NOW());";
query.execute();
query.reset();
query << "call CreateTable('test2', 'generic', 0, 1, 2, 3,4,5,6,7,8,9,10,NOW());";
query.execute();
query.reset();
return 0;
} else {
cerr << "DB connection failed: " << conn.error() << endl;
return 1;
}
return 0;
}
I want to use mysql++ query to execute procedure "CreateTable" many times, and i reset the query at last, but no matter how, just the first query works, the last does not, my problem is that:
how to make all of queries work?
-- create table --
delimiter $$
drop procedure if exists CreateTable $$
create procedure CreateTable(
IN tableName VARCHAR(20),
IN dbName VARCHAR(20),
IN INT_RegDevID INTEGER,
IN Dec_Long DECIMAL(24,16),
IN Dec_Lat DECIMAL(24,16),
IN Dec_Height DECIMAL(10,6),
IN Dec_Direction DECIMAL(10,6),
IN AverageSpeed DECIMAL(10,6),
IN Dec_Base VARCHAR(10),
IN MCC INTEGER,
IN MNC INTEGER,
IN LAC INTEGER,
IN CI INTEGER,
IN Dec_LocaDate TIMESTAMP)
-- -------------------------------------------------------------------------------
-- -------------------------------------------------------------------------------
begin
-- the test variable
-- Warning: the encoding can result many problem!!!
declare varTableName VARCHAR(32) default NULL;
set #varTableName = NULL;
set #table_prefix = "posinfo_";
set #table_params = "(
`Int_LocaID` int(11) NOT NULL auto_increment,
`INT_RegDevID` int(11) NOT NULL default '0',
`Dec_Long` decimal(24,16) NOT NULL default '0.0000000000000000',
`Dec_Lat` decimal(24,16) NOT NULL default '0.0000000000000000',
`Dec_Height` decimal(10,6) NOT NULL default '0.000000',
`Dec_Direction` decimal(10,6) NOT NULL default '0.000000',
`Dec_ MaxSpeed` decimal(10,6) NOT NULL default '0.000000',
`Dec_ MinSpeed` decimal(10,6) NOT NULL default '0.000000',
`AverageSpeed` decimal(10,6) NOT NULL default '0.000000',
`Var_PosInfo` varchar(50) character set latin1 NOT NULL default '',
`Var_Remark` varchar(200) character set latin1 NOT NULL default '',
`Date_LocaDate` timestamp NOT NULL default CURRENT_TIMESTAMP,
`Dec_Base` varchar(10) character set latin1 NOT NULL,
`MCC` int(11) NOT NULL COMMENT '',
`MNC` int(11) NOT NULL COMMENT '',
`LAC` int(11) NOT NULL COMMENT '',
`CI` int(11) NOT NULL COMMENT '',
PRIMARY KEY (`Int_LocaID`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=gbk;";
set #varCreate = CONCAT("create table ", dbName,".",#table_prefix, tableName, #table_params);
-- the insert operation
set #insertOperation = CONCAT("insert into ", dbName,".",#table_prefix, tableName,
"(INT_RegDevID,Dec_Long,Dec_Lat,Dec_Height,Dec_Direction,AverageSpeed,
Dec_Base,MCC,MNC,LAC,CI,Date_LocaDate) values(",INT_RegDevID,",",Dec_Long,
",",Dec_Lat,",",Dec_Height,",",Dec_Direction,",",AverageSpeed,",",Dec_Base,
",",MCC,",",MNC,",",LAC,",",CI,",NOW())");
-- find the target table
-- Look care about the "' '" !
set #getTargetTable = CONCAT("select TABLE_NAME into #varTableName from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='",
dbName, "' and TABLE_NAME='", #table_prefix, tableName,"'");
-- -------------------------------------------------------------------------------
-- -------------------------------------------------------------------------------
PREPARE getTargetTable from #getTargetTable;
execute getTargetTable;
select #varTableName;
set varTableName = #varTableName;
if varTableName is NULL then
-- create new table
PREPARE newTable
from #varCreate;
execute newTable;
-- do insert operation
PREPARE insertOperation
from #insertOperation;
execute insertOperation;
else
-- do insert operation
PREPARE insertOperation
from #insertOperation;
execute insertOperation;
end if;
end $$
delimiter ;
above, are the procedure.
There are several bugs here:
You've turned off exceptions (conn(false)) but you're also not checking return values for error codes. Your second execute() call is failing, but without asking the Query object why, you're running blind.
Instead of adding error checking to all MySQL++ calls, though, I think it's cleaner to allow MySQL++ to throw exceptions (conn()) and wrap the whole thing in a try block.
You don't need the MultiStatementsOption to do what you're asking the way you currently show. You have two separate statements here, not one compound statement. That in combination with the semicolons may be confusing MySQL, which is why the second call fails.
The mysql command line tool demands semicolons to terminate SQL statements, but when using a database API like MySQL++, they're only necessary to separate multiple statements.
You can either combine both CREATE statements into a single string (and one execute()) or you can drop the semicolons and the MultiStatementsOption.
The reset() calls between queries haven't been necessary since MySQL++ 2.x. The only reason the method is still available is that it's necessary if you want to reuse a Query object that had been used for template queries; they're the only type that still don't auto-reset, for fairly obvious reasons.
I'm trying to use an in-memory SQLite database to test my data layer which is provided by NHibernate.
I've read a load of blogs and articles about getting this setup but I'm now very confused as to why it isn't working.
The problem - when I run a unit test I get the error 'no such table: Student'. The articles I've read suggest this is because the schema isn't getting generated, or, the connection is being closed between my SchemaExport and query. I've checked everywhere I can think of and can't see how either of these scenarios are occuring.
My test output log looks like this:
OPEN CONNECTION
drop table if exists "Student"
drop table if exists "Tutor"
create table "Student" (
ID integer,
Name TEXT,
DoB DATETIME,
TutorId INTEGER,
primary key (ID)
)
create table "Tutor" (
ID integer,
Name TEXT,
primary key (ID)
)
NHibernate: INSERT INTO "Student" (Name, DoB, TutorId) VALUES (#p0, #p1, #p2); select last_insert_rowid();#p0 = 'Text1', #p1 = 01/12/2010 14:55:05, #p2 = NULL
14:55:05,750 ERROR [TestRunnerThread] AbstractBatcher [(null)]- Could not execute query: INSERT INTO "Student" (Name, DoB, TutorId) VALUES (#p0, #p1, #p2); select last_insert_rowid()
System.Data.SQLite.SQLiteException (0x80004005): SQLite error
no such table: Student
at System.Data.SQLite.SQLite3.Prepare(String strSql, SQLiteStatement previous, String& strRemain)
at System.Data.SQLite.SQLiteCommand.BuildNextCommand()
at System.Data.SQLite.SQLiteCommand.GetStatement(Int32 index)
at System.Data.SQLite.SQLiteDataReader.NextResult()
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd)
14:55:05,781 ERROR [TestRunnerThread] ADOExceptionReporter [(null)]- SQLite error
no such table: Student
DISPOSE
CLOSING CONNECTION
Originally I was using my own code for the connection/session management but have moved to the code in this blog post translated to C# and with a couple changes to the DBConfig method and some debug statements to show the state of the connection.
private FluentNHibernate.Cfg.Db.IPersistenceConfigurer GetDBConfig()
{
return SQLiteConfiguration.Standard
.ConnectionString((ConnectionStringBuilder cs) => cs.Is(CONNECTION_STRING))
.ProxyFactoryFactory("NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu")
.Raw("connection.release_mode", "on_close");
}
I added the on_close after reading this
My test code looks like this:
[Test]
public void CanGetStudentById()
{
using (var scope = new SQLiteDatabaseScope<StudentMapping>())
{
using (ISession sess = scope.OpenSession())
{
// Arrange
var repo = new StudentRepository();
repo.Save(new Student() { Name = "Text1", DoB = DateTime.Now });
// Act
var student = repo.GetById(1);
// Assert
Assert.IsNotNull(student);
Assert.AreEqual("Text1", student.Name);
}
}
}
What have I overlooked here?
Update: I created a copy of the class that connects to an SQLite file DB and it worked fine. So it has to be something to do with the connection being closed.
If you change your test method to the following, does it work?
[Test]
public void CanGetStudentById()
{
using (var scope = new SQLiteDatabaseScope<StudentMapping>())
{
using (ISession sess = scope.OpenSession())
{
// Arrange
sess.Save(new Student() { Name = "Text1", DoB = DateTime.Now });
// Act
var student = sess.Get<Student>(1);
// Assert
Assert.IsNotNull(student);
Assert.AreEqual("Text1", student.Name);
}
}
}
I would hazard to guess that your StudentRepository is opening its own session and hence doesn't see the table.
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!