How do I insert into a FoxPro table with a primary key whose default value isnewid("tablename") using Microsoft's OLE DB Provider for Visual Foxpro 9.0?
I have two tables, one where the primary key has a default value of newid("tablename"), and the other whose data type is set to Integer (AutoInc).
When I try to run the same insert command on the newid table, I get the following OleDbException:
Feature is not available
When I run insert on the table with Integer (AutoInc) as the primary key, it works.
Here is the code I am trying to execute:
public void InsertData()
{
using(var connectionHandler = new OleDbConnection("Provider=VFPOLEDB.1;Data Source=\"C:\\path\\to\\db.dbc\";"))
{
var insertStatement = #"INSERT INTO mytable (mycol) values (?)";
var insertCommand = new OleDbCommand(insertStatement, connectionHandler);
insertCommand.Parameters.Add("mycol", OleDbType.Char).Value="blue";
connectionHandler.Open();
insertCommand.ExecuteNonQuery();
connectionHandler.Close();
}
}
Is there any reason OLE would not be able to execute an insert because of this newid() default value?
Another case that fixes my issue is if I manually specify an id in the insert clause. ex:
var insertStatement = #"INSERT INTO mytable (id, mycol) values (?, ?)";
insertCommand.Parameters.Add("id", OleDbType.Integer).Value = 199;
insertCommand.Parameters.Add("mycol", OleDbType.Char).Value = "blue";
Additionally, I am able to run a select * from tablename on this table.
select snippet:
public DataTable GetData()
{
var myData = new DataTable();
using(var connectionHandler = new OleDbConnection("Provider=VFPOLEDB.1;Data Source=\"C:\\path\\to\\db.dbc\";"))
{
var da = new OleDbDataAdapter();
var mySQL = "select * from mytable";
var myQuery = new OleDbCommand(mySQL, connectionHandler);
connectionHandler.Open();
da.SelectCommand = myQuery;
da.Fill(myData);
connectionHandler.Close();
}
return myData;
}
newid() SP:
function newId
parameter thisdbf
regional keynm, newkey, cOldSelect, lDone
keynm=padr(upper(thisdbf),50)
cOldSelect=alias()
lDone=.f.
do while not lDone
select keyvalue from main!idkeys where keyname=keynm into array akey
if _tally=0
insert into main!idkeys (keyname) value (keynm)
loop
endif
newkey=akey+1
update main!idkeys set keyvalue=newkey where keyname=keynm and keyvalue=akey
if _tally=1
lDone=.t.
endif
enddo
if not empty(cOldSelect)
select &cOldSelect
else
select 0
endif
return newkey
In a separate stored procedure, I have tried the following to isolate the key word regional:
FUNCTION GetHello
regional hello
RETURN "HELLO WORLD"
Which seems to work (for example, setting the default value of a column to GetHello() then running an insert over OLEDB).
The following causes my stored procedure to fail with "Feature is not available" over OLEDB:
FUNCTION GetHello
LOCAL test
test="select * from customer"
&test
RETURN "hello"
Thanks for sharing the SP code! It shows what was the culprit.
Though my nerves tend to do many corrections on that code I will only modify enough to make it work from both VFP and C#:
function newId
parameter thisdbf
regional keynm, newkey, nOldSelect, lDone
keynm=padr(upper(thisdbf),50)
nOldSelect=select()
lDone=.f.
do while not lDone
select keyvalue from main!idkeys where keyname=keynm into array akey
if _tally=0
insert into main!idkeys (keyname) value (keynm)
loop
endif
newkey=akey+1
update main!idkeys set keyvalue=newkey where keyname=keynm and keyvalue=akey
if _tally=1
lDone=.t.
endif
enddo
Select (m.nOldSelect)
return newkey
The changed part is only related to that (if !empty(...)) block.
The issue is this variable declaration line in the stored procedure:
regional keynm, newkey, cOldSelect, lDone
The 'regional' keyword is a holdover from the old FoxPro 2.6 for DOS\Windows. It was only used in programs generated by the screen builder tool. It obviously still lurks within Visual FoxPro to support functionality that migrates old FoxPro 2.6 projects to Visual FoxPro, because the code works fine in VFP. But the OLEDB driver doesn't support it.
Anyway, you need to change your stored procedure to use:
local keynm, newkey, cOldSelect, lDone
... and recompile it, for which you will need Visual FoxPro. Ensure there are no OLEDB or other connections to it and then in the Visual FoxPro command window:
compile c:\path\to\mydatabase.dbc
Related
Currently, I am working on an inventory system that will allow the client to customize their own SQL Server tables.
The user will name their column then specify the data type they would like to use. After they are satisfied with their table they click on the submit table button to create their table. C# then will create a unique table for the user by running a SQL Server stored procedure.
SqlConnection connection = new SqlConnection(Constants.conn);
connection.Open();
string query = "EXEC CreateTable #TableName='"+ UserAccountInfo.Username + "InventoryTable'";
SqlCommand command = new SqlCommand(query, connection);
command.ExecuteReader();
Then using a foreach loop, iterating through each column added (class UserTable is my custom WPF UserControl) by looking at the Stack panel "StackTable" Children, and assigning the properties of the Usertable "Name" and "DataType" to the List of String Arrays. Then I iterate throughout the list and execute the second stored procedure that alters the table previously created.
I believe all of this to work!! My question and problem lies in my stored procedure.
List<string[]> list = new List<string[]>();
foreach (UserTable ut in StackTable.Children)
{
string dataType = "";
switch (ut.DataType.Text)
{
case "Text": dataType = "Varchar(100)"; break;
case "Number": dataType = "INT"; break;
case "Boolean": dataType = "Varchar(5)"; break;
}
string[] vs = {"[" + ut.Name.Text + "]", dataType};
list.Add(vs);
}
foreach (string[] s in list)
{
string query2 = "EXEC SelectAllCustomers #Name = '" + s[0] + "', #DataType = '" + s[1] + "';";
SqlCommand command2 = new SqlCommand(query, connection);
command2.ExecuteReader();
}
My DATABASE Schema consists of the base database called InventoryDatabase, inside a table called LoginInfo where I store the user's information, and the rest of the tables are going to be the tables we are creating above. PROBLEM IS, my stored procedures are throwing syntax errors.
You'll have to use dynamic SQL. Here's an example to get you started:
create or alter procedure CreateTable #TableName varchar(100)
as
begin
declare #sql nvarchar(max) =
concat('create table ',quotename(#TableName),' AccountId int primary key identity(1,1)');
exec (#sql);
end
Note that the name is passed through the quotename function to protect against SQL Injection.
Option 1: prepare the SQL script as a string which can able to Create or alter the table in your C# code and then execute prepared string using ExecuteNonQuery method.
Option 2: by using dynamic SQL concept, prepare the create/alter table script with in a stored procedure and then execute
I have a SQL statement that I need to run in C# and would need to get parameters from C# code. I know stored procedures are preferred to avoid SQL injection but I am just looking to do this in C#.
I am translating this SQL to C# but I encountered an error even though the query works in SQL Server Management Studio. It uses temporary stored procedure and temp table below:
-- 1.) Declare a criteria table which can be any number of rows
BEGIN TRY
DROP TABLE #CriteriaTable
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE #CriteriaTable (ParameterCode VARCHAR(64), Value VARCHAR(64))
-- 2.) Declare a procedure to add criteria table
BEGIN TRY
DROP PROCEDURE #AddCriteriaTable
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #AddCriteriaTable
(#ParameterCode VARCHAR(64), #Value VARCHAR(64))
AS
INSERT #CriteriaTable
VALUES(#ParameterCode, #Value)
GO
-- 3.) Do a computation which accesses the criteria
BEGIN TRY
DROP PROCEDURE #ComputeBasedOnCriteria
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #ComputeBasedOnCriteria
(#product VARCHAR(36) = 'ABC',
#currency VARCHAR(3) = 'USD',
#zScore FLOAT = .845)
AS
-- Code inside this procedure is largely dynamic sql.
-- This is just a quick mock up
SELECT
#Product ProductCode,
#currency Currency,
950 ExpectedRevenue,
*
FROM
#CriteriaTable c
PIVOT
(min (Value) FOR ParameterCode IN
([MyParam1], MyParam2, MyParam3)
) AS pvt
GO
--End of code for Configuration table
-- Samples: Execute this to add criteria to the temporary table that will be used by #ComputeBasedOnCriteria
EXEC #AddCriteriaTable 'MyParam1', 'MyValue1'
EXEC #AddCriteriaTable 'MyParam2', 'MyValue3'
EXEC #AddCriteriaTable 'MyParam3', 'MyValue3'
--Execute the procedure that will return the results for the screen
EXEC #ComputeBasedOnCriteria
Now trying this in C# I encounter an error when I try to run the #AddCriteriaTable procedure. When I try to run the ExecuteQuery on the second to the last line it throws:
Exception: System.Data.SqlClient.SqlException, Incorrect syntax near the keyword 'PROC'.
Why does it work in SQL Server but not in C# code? Is there another way to do this in C#? Let me know if there are c# guidelines I should follow as I am still learning this c# - db work.
EDIT:
I know I could do this as a normal stored proc and pass in a DataTable however there are team issues I cannot say and it forces me to use the sp as a text.
The reason that it is failing is you are passing parameters to the CREATE PROC section here:
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
It does not make sense to pass the values here, since you are just creating the procedure, you only need to pass them when executing the procedure. If you run a trace you will see something like this being executed on the server:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)',
N'#ParameterCode VARCHAR(64),#Value VARCHAR(64)',
#ParameterCode = 'MyParam1',
#Value = 'MyValue1'
Which will throw the same incorrect syntax error when run in SSMS. All you need is:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)';
So in c# you would need:
//First Create the procedure
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.ExecuteNoneQuery();
//Update the command text to execute it, then add parameters
cmd.CommandText = "EXECUTE #AddCriteriaTable #ParameterCode, #Value;";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
I think you are over complicating everything, a temporary stored procedure to add data to a temporary table seems over kill.
If you are executing from code it seems likely that you need to reuse everything, so why not just have a permanent procedure for your computation,
and then use a defined type to manage instances of the execution.
So first create your type:
CREATE TYPE dbo.CriteriaTableType AS TABLE (ParameterCode VARCHAR(64), Value VARCHAR(64));
Then create your procdure:
CREATE PROC dbo.ComputeBasedOnCriteria
(
#product VARCHAR(36)='ABC',
#currency VARCHAR(3)='USD',
#zScore FLOAT = .845,
#CriteriaTable dbo.CriteriaTableType READONLY
)
AS
--Code inside this proc is largely dynamic sql. This is just a quick mock up
SELECT
#Product ProductCode
,#currency Currency
,950 ExpectedRevenue
,*
FROM #CriteriaTable c
PIVOT (MIN (Value) FOR ParameterCode IN (MyParam1, MyParam2,MyParam3)) AS pvt;
GO
Then finally to run:
DECLARE #Criteria dbo.CriteriaTableType;
INSERT #Criteria
VALUES
('MyParam1', 'MyValue1'),
('MyParam2', 'MyValue2'),
('MyParam3', 'MyValue3');
EXECUTE dbo.ComputeBasedOnCriteria #CriteriaTable = #Criteria;
You can even populate the criteria table in c#, and just pass this from c# to the procedure.
var table = new DataTable();
table.Columns.Add("ParameterCode", typeof(string)).MaxLength = 64;
table.Columns.Add("Value", typeof(string)).MaxLength = 64;
foreach (var criterion in request.Criteria)
{
var newRow = table.NewRow();
newRow[0] = criterion.Key;
newRow[1] = criterion.Value;
table.Rows.Add(newRow);
}
using (var connection = new SqlConnection("connectionString"))
using (var command = new SqlCommand("dbo.ComputeBasedOnCriteria", connection))
{
var tvp = command.Parameters.Add("#CriteriaTable", SqlDbType.Structured);
tvp.TypeName = "dbo.CriteriaTableType";
tvp.Value = table;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
//Do Something with your results
}
}
}
If you are executing SQL to create a stored procedure via C# then you might as well just execute your SQL via C# and forget about the Procedure.
The point of using a stored procedure to avoid SQL Injection only applies when the stored procedure already exists on the server and you are not creating it via the code.
You can avoid SQL injection here by using a Parameterised query.
Parameters prevent sql injection by validating the data type. So if you insert a integer in your code then someone attempting injection cannot supply a string with special characters which changes your expected result.
BUT apart from all that, you're getting an error because you have CREATE PROC in your SQL in C# instead of CREATE PROCEDURE
Just a few years behind, but I discovered table valued parameters for stored procedures/UDFs today. They are the ideal solution to a problem I'm having, but I can't get them to work from C#.
I have a UDF:
CREATE FUNCTION GetSurveyScores
#Survey bigint,
#Question nvarchar(max),
#Area StringList READONLY,
#JobCode StringList READONLY
AS BEGIN
SELECT * FROM SurveyResults WHERE RespondentArea IN (Select val from #Area) AND RespondentJobCode IN (select val from #JobCodes)
END
(StringList is the type I created, it's just a table valued type with a single column called val, defined as nvarchar(256))
Then from SQL Server Management Studio, I can do this:
declare #area StringList;
insert into #area(val) values('NW'),('NE'),('SW');
// Get all survey respondent job codes from the SurveyRespondent table.
declare #jobcodes StringList;
insert into #jobcodes select distinct jobcode from dbo.SurveyRespondent;
select * from dbo.GetSurveyScores(3, 'Q3', #area, #jobcodes)
That works brilliantly.
From C#, I get no results (no exceptions), using this code: (I'm using DataTables because the actual code I intend to drop this into uses DataTables already)
DataTable areas = new DataTable("StringList");
areas.Columns.Add("val");
areas.Rows.Add("NW"); areas .Rows.Add("NE"); areas .Rows.Add("SW");
DataTable jobcodes = new DataTable("StringList");
jobcodes.Columns.Add("val");
jobcodes.Rows.Add("JC1"); jobcodes .Rows.Add("JC2");
SqlCommand cmd = new SqlCommand("SELECT * FROM dbo.GetSurveyScores(3, 'Q3', #area, #jc)", connection);
cmd.Parameters.AddWithValue("#area", areas);
cmd.Parameters["#area"].SqlDbType = SqlDbType.Structured;
cmd.Parameters["#area"].TypeName = "StringList";
cmd.Parameters.AddWithValue("#jc", jobcodes);
cmd.Parameters["#jc"].SqlDbType = SqlDbType.Structured;
cmd.Parameters["#jc"].TypeName = "StringList";
DataTable results = new DataTable();
using (SqlDataAdapter da = new SqlDataAdapter(cmd)) {
da.Fill(results);
}
Console.WriteLine(results.Rows.Count);
The final line prints 0. I'm sure I must be missing something simple, but after 6 hours of trying, I think I need a new set of eyes to look at it.
I believe that the issue is that you are not returning anything from your function, although to be honest I'm not sure how it was able to run in MGT studio because it looks like a syntax error. The function should look something like this
CREATE FUNCTION GetSurveyScores (
#Survey bigint,
#Question nvarchar(max),
#Area StringList READONLY,
#JobCodes StringList READONLY )
RETURNS #Results TABLE (RespondentArea NVARCHAR(256), RespondentJobCode NVARCHAR(256))
AS
BEGIN
INSERT #Results
SELECT * FROM SurveyResults WHERE RespondentArea IN (Select val from #Area) AND RespondentJobCode IN (select val from #JobCodes)
RETURN
END
go
Id' image that the syntax you actually used was slightly different, and the select statement is getting executed but the return value of the function has no results.
I have a stored procedure which has been well tested and works perfectly from SQL Server Management Studio. All the procedure does is check for the existence of a record in a table, return it if it exists, or create it and then return it if it doesn't.
The procedure looks like this:
CREATE proc [dbo].[spInsertSerialBatch]
#MOS_JOB varchar(12), --PASSED COMMAND LINE
#MOS_LOT varchar(4) = NULL, --PASSED COMMAND LINE
#MES_USER varchar(12) = 'NOT PASSED',--PASSED COMMAND LINE
#COMPUTERNAME varchar(100) = 'NOT PASSED' --ENVIRONMENT VARIABLE
as ....
I use a SqlDataAdapter, which I have used repeatedly without any problems. The setup looks like this:
using (SqlCommand sqlComm = new SqlCommand("dbo.spInsertSerialBatch", serialBatchDataConnection))
{
if (serialBatchDataConnection.State != ConnectionState.Open)
{
serialBatchDataConnection.Open();
}
sqlComm.CommandType = CommandType.StoredProcedure;
sqlComm.Parameters.AddWithValue("#MOS_JOB", options.jobNumber);
sqlComm.Parameters.AddWithValue("#MOS_LOT", options.lotNumber);
sqlComm.Parameters.AddWithValue("#MES_USER", options.userId);
sqlComm.Parameters.AddWithValue("#COMPUTERNAME", System.Environment.MachineName);
SqlDataAdapter sda = new SqlDataAdapter(sqlComm);
DataTable dt = new DataTable();
int rowsAffected = sda.Fill(dt);
}
I then examine the results of the table after Fill is executed. It works fine when the row exists in the table, but if it doesn't, and the stored proc needs to generate it, Fill returns 0 rows and the data table remains empty. No errors/exceptions are thrown, I just get no results.
I suppose I could change the code to use ExecuteNonQuery and not use the DataAdapter, but I see no reason why this shouldn't just work; I prefer having a data table (which may result in more than a single row in some cases) than using a data reader and looping over the data to get the results.
Any suggestions as to why this might fail? I've looked over several posts on this and other sites that are similar, but haven't found a satisfactory answer. Any suggestions are greatly appreciated.
Thanks,
Gary
The entrire sp is quite large and probably too proprietary to publish...
--return inserted rows
SELECT 'CREATED' as [spRESULT], o.*
FROM #output o
END
/*
* Return existing SerialBatch(s)
*/
BEGIN
SELECT 'RETRIEVED' as [spRESULT], s.*
FROM SerialBatch s
WHERE SerialBatchId = #formattedId
UNION
/*
* pull in products that have components that need labels as well
*/
SELECT 'RETRIEVED' as [spRESULT],s.*
FROM SerialBatch s
WHERE SerialBatchParentId = #formattedId
END
This is the end of the stored procedure. I tried executing a DataReader instead and the result is the same...I get no results for the case when the sp has to create it, but again it runs perfectly stand-alone in SQL Server Management Studio.
Problem solved. Turns out that the OpenQuery string passed to Oracle was converting an empty string to a NULL and preventing the new row from being returned. All I need to add was a check for both NULL and empty string:
if #MOS_LOT IS NULL or #MOS_LOT = ''
set #MOS_LOT = ' ' --EMPTY STRINGS BEING EQUIVALENT TO NULLS
I know that in Oracle I can get the generated id (or any other column) from an inserted row as an output parameter.
Ex:
insert into foo values('foo','bar') returning id into :myOutputParameter
Is there a way to do the same, but using ExecuteScalar instead of ExecuteNonQuery?
I don't want to use output parameters or stored procedures.
ps: I'm using Oracle, not sql server!!!
If you are on oracle, you have to use ExecuteNonQuery and ResultParameter. There is no way to write this as query.
using (OracleCommand cmd = con.CreateCommand()) {
cmd.CommandText = "insert into foo values('foo','bar') returning id into :myOutputParameter";
cmd.Parameters.Add(new OracleParameter("myOutputParameter", OracleDbType.Decimal), ParameterDirection.ReturnValue);
cmd.ExecuteNonQuery(); // an INSERT is always a Non Query
return Convert.ToDecimal(cmd.Parameters["myOutputParameter"].Value);
}
Oracle uses sequences as for his identity columns, if we may say so.
If you have set a sequence for your table primary key, you also have to write a trigger that will insert the Sequence.NextValue or so into your primary key field.
Assuming that you are already familiar with this concept, simply query your sequence, then you will get your answer. What is very practiced in Oracle is to make yourself a function which will return an INT, then within your function, you perform your INSERT. Assuming that you have setup your trigger correctly, you will then be able to return the value of your sequence by querying it.
Here's an instance:
CREATE TABLE my_table (
id_my_table INT PRIMARY KEY
description VARCHAR2(100) NOT NULL
)
CREATE SEQUENCE my_table_seq
MINVALUE 1
MAXVALUE 1000
START WITH 1
INCREMENT BY 2
CACHE 5;
If you want to manage the auto-increment yourself, here's how:
INSERT INTO my_table (
id_my_table,
description
) VALUES (my_table_seq.NEXTVAL, "Some description");
COMMIT;
On the other hand, if you wish not to care about the PRIMARY KEY increment, you may proceed with a trigger.
CREATE OR REPLACE TRIGGER my_table_insert_trg
BEFORE INSERT ON my_table FOR EACH ROW
BEGIN
SELECT my_table_seq.NEXTVAL INTO :NEW.id_my_table FROM DUAL;
END;
Then, when you're inserting, you simply type the INSERT statement as follows:
INSERT INTO my_table (description) VALUES ("Some other description");
COMMIT;
After an INSERT, I guess you'll want to
SELECT my_table_seq.CURRVAL
or something like this to select the actual value of your sequence.
Here are some links to help:
http://www.orafaq.com/wiki/Sequence
http://www.orafaq.com/wiki/AutoNumber_and_Identity_columns
Hope this helps!
You can use below code.
using (OracleCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"INSERT INTO my_table(name, address)
VALUES ('Girish','Gurgaon India')
RETURNING my_id INTO :my_id_param";
OracleParameter outputParameter = new OracleParameter("my_id_param", OracleDbType.Decimal);
outputParameter.Direction = ParameterDirection.Output;
cmd.Parameters.Add(outputParameter);
cmd.ExecuteNonQuery();
return Convert.ToDecimal(outputParameter.Value);
}
one possible way if one can add one column named "guid" to the table :
when inserting one record from c#, generate a guid and write it to the guid column.
then perform a select with the generated guid, and you have got the id of inserted record :)
Select t.userid_pk From Crm_User_Info T
Where T.Rowid = (select max(t.rowid) from crm_user_info t)
this will return your required id