I'm trying to do a multiple insert query using a parameterized command but I am getting a syntax exception thrown. The query in sql would look like:
CREATE TEMPORARY TABLE IF NOT EXISTS TEMP_PTO (
ng_id INT,
requested_hours DECIMAL(7,4)
);
INSERT INTO TEMP_PTO VALUES
(1, 0.0000), (2, 1.5000);
I generate it in C# via the following where ptoHours is an IEnumerable of a class containing the ng_id and the requested_hours. Queries is an resx file containing the query text, and Queries.PTOLoadTempCommand = INSERT INTO TEMP_PTO VALUES #v;:
using (var conn = new MySqlConnection(this.connString))
{
MySqlTransaction trans = null;
try
{
await conn.OpenAsync();
trans = conn.BeginTransaction();
using (var tempCommand = conn.CreateCommand())
{
tempCommand.CommandText = Queries.PTOTempTableCommand;
await tempCommand.ExecuteNonQueryAsync();
}
using (var loadCommand = conn.CreateCommand())
{
loadCommand.CommandText = Queries.PTOLoadTempCommand; //INSERT INTO TEMP_PTO VALUES #v;
loadCommand.Parameters.AddWithValue("#v", String.Join(", " ,ptoHours.Select(p => String.Format("({0}, {1:N4})", p.AgentId, p.UsedVacationHours))));
//Exception thrown here
var affected = await loadCommand.ExecuteNonQueryAsync();
if (affected != entryCount)
throw new Exception("Not All Entries Loaded");
}
trans.Commit();
}
catch (Exception e)
{
if(trans!= null) trans.Rollback();
}
}
Looking at the loadCommand object, I can see that the value of the parameter #v is (1, 0.0000), (2, 1.5000). Are you able to do an insert like this or do I need to modify the function to insert them one at a time? I know I could go the StringBuilder route, but then I cannot use the safety of the parameterization.
You will have to do it one at a time. The query parameters are parsed and it is not allowed to have a parameter in that position.
There shouldn't be a need to insert rows in one command, if you need both to succeed or fail, just use transactions.
And as you said, using a StringBuilder or other string catenations is not smart.
Related
I am using ADO.NET for querying the SQL Server database. I am trying to get items if present on the table.
My query is executing but returning nothing even if there is.
Here is my code:
public List<string> GetRecords(List<string> itemList)
{
itemList.Add("100");
string list = string.Join(",", itemList.Select(x => string.Format("'{0}'", x)));
string query = #"SELECT Id FROM Employees WHERE Id In (#list)";
using (SqlCommand sqlCommand = new SqlCommand(query,connection))
{
sqlCommand.Parameters.AddWithValue("#list", list);
sqlDataReader = sqlCommand.ExecuteReader();
while (sqlDataReader.Read())
{
employeeList.Add(Convert.ToString(database.Sanitise(sqlDataReader, "Id")));
}
}
}
There are three items in the list the employee with ID=100 is available in the table but the other two's are not. but still the query returning nothing.
SQL profiler showing me this query:
exec sp_executesql N'SELECT
Id
FROM
Employees
WHERE
Id In (#list)',N'#list nvarchar(29)',#list=N'''50'',''23'',''100'''
SQL Server will not interpret your concatenated list as actual code. It remains data always, so it's just one big text string of numbers. That is never going to match a single row.
Instead, use a Table Valued Parameter.
First create a table type in your database, I usually keep a few useful ones around.
CREATE TYPE dbo.IdList (Id int PRIMARY KEY);
Then create a DataTable and pass it as a parameter.
public List<string> GetRecords(List<string> itemList)
{
var table = new DataTable { Columns = {
{ "Id", typeof(int) },
} };
foreach (var id in itemList)
table.Rows.Add(id);
const string query = #"
SELECT e.Id
FROM Employees e
WHERE e.Id IN (SELECT l.Id FROM #list l);
";
using (var connection = new SqlConnection(YourConnString)) // always create and dispose a new connection
using (var sqlCommand = new SqlCommand(query,connection))
{
sqlCommand.Parameters.Add(new SqlParameter("#list", SqlDbType.Structured) {
Value = table,
TypeName = "dbo.IdList",
});
connection.Open();
using (var sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
employeeList.Add((string)sqlDataReader["Id"]);
}
}
}
}
Note also:
using on all SQL objects.
Do not cache a connection object. Create when you need it, dispose with using.
I don't know what your Sanitize function does, but it probably doesn't work. Sanitizing database values correctly is hard, you should always use parameterization.
AddWithValue is a bad idea. Instead specify the parameter types (and lengths/precision) explicitly.
I'm trying to Insert multiple records to table using ado.net, and print id inserted.
My code is like this:
List<string> listQuery = new List<string>()
{
"INSERT INTO Students (Name) VALUES ('student1');SELECT ##Identity;",
"INSERT INTO Students (Name) VALUES ('student2');SELECT ##Identity;",
"INSERT INTO Students (Name) VALUES ('student3');SELECT ##Identity;",
};
using (SqlConnection connection = new SqlConnection(_connectionString))
{
try
{
connection.Open();
using (SqlCommand command = new SqlCommand())
{
command.Connection = connection;
foreach (var myQuery in listQuery)
{
command.CommandText = myQuery;
int id = Convert.ToInt32((decimal)command.ExecuteScalar());
Console.WriteLine("Inserted: " + id);
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
//Close and dispose
connection.Close();
}
}
I wondering, whether should I execute every command like that? Or concatenate all query and execute just a times?.
If I should execute one times. How can i get all id of records inserted?
you can use the OUTPUT clause to return the identity id
INSERT INTO Students (Name)
OUTPUT INSERTED.id
VALUES ('student1'), ('student2'), ('student3');
Don't go like this, and DON'T call database in ANY loop.
For resolving your problem, you should write stored procedure that take a DataTable as input of your student list (tutorial) or use JSON string as input. In that stored procedure you use OUTPUT clause to retrieve id: OUTPUT INSERTED.id . On C# code, you only need ExecuteReader to get all id.
What I need:
In PLS/SQL on an Oracle DB, create a stored procedure or function with parameters, which given a declared table of , where is a ROW of a table (with all the fields), returns the resultset following the conditions given in the parameters. After, I need to call them from Microsoft Entity Framework with edmx file.
Basically the need is to being able to provide a quick report of the table contents into a pdf, matching some filters, with an oracle db.
The mantainer must be able, provided a script I give, to create and add new reports, so this needs to be dynamic.
Here's what I've got so far:
CREATE OR REPLACE type THETABLEIWANTTYPE as table of THETABLEIWANT%TYPE
create function
SCHEMA.THETABLEIWANT_FUNCTION(PARAM_GR in number default 1)
return THETABLEIWANTTYPE
PIPELINED
as
result_table THETABLEIWANTTYPE
begin
SELECT S.id, S.idg, S.sta, S.tab
Bulk collect into result_table
from SCHEMA.THETABLEIWANT S
WHERE IDGR = PARAM_GR
IF result_table.count > 0 THEN
for i in result_table.FIRST .. result_table.LAST loop
pipe row (result_table(i))
end loop
end if
return
end;
But it's not working. It gives errors.
Running CREATE TYPE I get:
Compilation errors for TYPE SCHEMA.THETABLEIWANT
Error: PLS-00329: schema-level type has illegal reference to
SCHEMA.THETABLEIWANT
The mantainer will launch the script creating a TYPE of the row of the table I need, then the function should return a table with the records.
Then calling it from Entity Framework I should be able to execute it like I'm calling a normal select from my table, IE:
``_dbContext.THETABLEIWANT.Where(x => x.IDGR = Param_gr).ToList();
The problem is that mantainers should be able to generate new kind of reports with any select inside without the need of my intervention on the software code.
Any hint?
It's ok also to bulk all the select result into a temp table but it has to be dynamic as column will be changing
I ended up to write a PLS/SQL procedure that returns a cursor and managing it from C# code with Oracle.ManagedDataAccess Library.
Here's the procedure, for anyone interested:
CREATE OR REPLACE PROCEDURE SCHEMA.PROC_NAME(
PARAM_1 VARCHAR2,
RESULT OUT SYS_REFCURSOR)
IS
BEGIN
OPEN RESULT FOR
SELECT A, V, C AS MY_ALIAS from SCHEMA.TABLE WHERE FIELD = PARAM_1 AND FIELD_2 = 'X';
END;
And here's the C# code for calling and getting the result:
OracleConnection conn = new OracleConnection("CONNECTIONSTRING");
try
{
if (conn.State != ConnectionState.Open)
conn.Open();
List<OracleParameter> parametri = new List<OracleParameter>()
{
new OracleParameter
{
ParameterName = nameof(filter.PARAM_1),
Direction = ParameterDirection.Input,
OracleDbType = OracleDbType.NVarchar2,
Value = filter.PARAM_1
}
};
OracleCommand cmd = conn.CreateCommand();
cmd.Parameters.AddRange(parametri.ToArray());
OracleParameter cursor = cmd.Parameters.Add(
new OracleParameter
{
ParameterName = "RESULT",
Direction = ParameterDirection.Output,
OracleDbType = OracleDbType.RefCursor
}
);
cmd.CommandText = procedureName;
cmd.CommandType = CommandType.StoredProcedure;
cmd.ExecuteNonQuery();
using (OracleDataReader reader = ((OracleRefCursor)cursor.Value).GetDataReader())
{
if (reader.HasRows)
while (reader.Read())
{
//Iterate the result set
}
}
}
catch(Exception ex)
{
//Manage exception
}
I need to display the result from a select statement in a stored procedure onto the textbox and I can't figure out how to do it. The select statement doesn't use a WHERE clause. The stored procedure goes
CREATE PROCEDURE NewCustomer
AS
BEGIN
SELECT MAX(ID) + 1 FROM Database
END
This is what I've tried
protected void btnNew_Click(object sender, EventArgs e)
{
Clear();
int num;
try
{
using (SqlCommand command = new SqlCommand("NewCustomer"))
{
command.Connection = conn;
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#CustID", SqlDbType.Int).Value = Int32.TryParse(txtCID.Text, out num); // Use tryparse if needed
conn.Open();
txtCID.Text = (string)command.ExecuteScalar();
}
}
catch (Exception ex)
{
lblMessage.Text = ex.Message;
}
}
It gives me a "Procedure NewCID has no parameters and arguments were supplied." Error
You are not executing the procedure that you ware given. The procedure is named as yadayada(The worst name that you can give) and you are executing the procedure NewCustomer as the command text. Both has to be same. Then you are using the Wrong statement for executing the query.
The ExecuteNonQuery to perform catalog operations (for example,
querying the structure of a database or creating database objects such
as tables), or to change the data in a database without using a
DataSet by executing UPDATE, INSERT, or DELETE statements.
But you are using it for executing the select query. Here you are selecting a single value from the table so the ExecuteScalar will be the best option for you. Your code will be like this: assume the procedure name is GetNewCustomerID;
using (SqlCommand exeCommand = new SqlCommand("GetNewCustomerID"))
{
exeCommand.Connection = conn;
exeCommand.CommandType = CommandType.StoredProcedure;
exeCommand.Parameters.Add("#CustID",SqlDbType.Int).Value=Convert.ToInt32(txtCID.Text); // Use tryparse if needed
conn.Open();
txtCID.Text = (string)exeCommand.ExecuteScalar();
}
I am trying to insert data using a stored procedure that has two tables. This first table is data is through text boxes the second data is through a grid which I stored in the database and passed to be inserted. The problem is when reading datatable and inserting it says there are too many parameter which happens to add in the for loop. Any suggestion how to handle this as the SP? Thanks in advance.
CODE:
try
{
SqlConnection conn = new SqlConnection();
conn.ConnectionString = strConnection;
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandTimeout = 120;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "insFamilyDetails";
cmd.Parameters.AddWithValue("#strHusbandName", strHusbandName);
cmd.Parameters.AddWithValue("#strRelation", strRelation);
....
....
// Child Details
for (int i = 0; i < strChildredDetails.Rows.Count; i++)
{
cmd.Parameters.AddWithValue("#strChildName", strChildredDetails.Rows[i][0].ToString());
cmd.Parameters.AddWithValue("#strDOB", strChildredDetails.Rows[i][1]);
cmd.Parameters.AddWithValue("#strBaptisedon", strChildredDetails.Rows[i][2]);
cmd.Parameters.AddWithValue("#strFirstComOn", strChildredDetails.Rows[i][3]);
cmd.Parameters.AddWithValue("#strConfirmedOn", strChildredDetails.Rows[i][4]);
cmd.Parameters.AddWithValue("#strMarried", "0");
cmd.Parameters.AddWithValue("#strAlive", "1");
}
conn.Open();
ReturnValue = Convert.ToBoolean(cmd.ExecuteNonQuery());
conn.Close();
}
catch (Exception e)
{
DL_LogAppErrors(e.ToString(), System.Reflection.MethodBase.GetCurrentMethod().Name, "Insert Family Details");
return ReturnValue;
}
return ReturnValue;
I assume from the code you're going to add into a main table, and Child table. For this case, you need to separate the process into two:
Add the data for in main table
Loop to add the child data
Note: you need to clear the parameters before adding a new set, OR instead of adding new parameters, change the value of existing parameters
EDIT: Using Transaction
con.Open();
SqlTransaction trans = con.BeginTransaction();
try {
// Execute the SP here
// After all SP executed, call the commit method
trans.Commit();
} catch (Exception ex) {
// An error happened, rollback
trans.RollBack();
}
con.Close();
You are adding parameters in command in each iteration of the loop. After first iteration you are trying to add same parameter name in parameter collection. You probably need to clear the collection of parameter on each iteration using SqlParameterCollection.Clear. Clear the parameter collection after executing command (In loop body).
conn.Open();
for (int i = 0; i < strChildredDetails.Rows.Count; i++)
{
cmd.Parameters.AddWithValue("#strChildName", strChildredDetails.Rows[i][0].ToString());
cmd.Parameters.AddWithValue("#strDOB", strChildredDetails.Rows[i][2]);
cmd.Parameters.AddWithValue("#strBaptisedon", strChildredDetails.Rows[i][2]);
cmd.Parameters.AddWithValue("#strFirstComOn", strChildredDetails.Rows[i][3]);
cmd.Parameters.AddWithValue("#strConfirmedOn", strChildredDetails.Rows[i][4]);
cmd.Parameters.AddWithValue("#strMarried", "0");
cmd.Parameters.AddWithValue("#strAlive", "1");
ReturnValue = Convert.ToBoolean(cmd.ExecuteNonQuery());
cmd.Parameters.Clear();
}
conn.Close();
If you have many records to insert in a table then you can send the comma separated values in SP and split then in SP and insert them. It will save db calls. This post will show how you can do that.
For each row you want to insert you have to call the ExecuteNonQuery() function ie, it should be inside the for loop and after that clear the parameter collection at the end of loop.
conn.Open();
// Child Details
for (int i = 0; i < strChildredDetails.Rows.Count; i++)
{
cmd.Parameters.AddWithValue("#strHusbandName", strHusbandName);
cmd.Parameters.AddWithValue("#strRelation", strRelation);
....
....
cmd.Parameters.AddWithValue("#strChildName", strChildredDetails.Rows[i][0].ToString());
cmd.Parameters.AddWithValue("#strDOB", strChildredDetails.Rows[i][1]);
cmd.Parameters.AddWithValue("#strBaptisedon", strChildredDetails.Rows[i][2]);
cmd.Parameters.AddWithValue("#strFirstComOn", strChildredDetails.Rows[i][3]);
cmd.Parameters.AddWithValue("#strConfirmedOn", strChildredDetails.Rows[i][4]);
cmd.Parameters.AddWithValue("#strMarried", "0");
cmd.Parameters.AddWithValue("#strAlive", "1");
ReturnValue = Convert.ToBoolean(cmd.ExecuteNonQuery());
cmd.Parameters.Clear();
}
As already said, you need to have ExecuteNonQuery inside for each loop, if you want to insert records of your grid.
Alternate option would be to Use Table Valued Paramter if you're using SQL Server 2008, that would make life more easy and you don't have to make round trip for each record of your gridview. Just pass the datatable.
Please check this link.
Edit:
For SQL Server 2005, you might want to use XML. Please check this link.
public string SerializeObject<T>(T Obj)
{
string strxml = string.Empty;
using (StringWriter sw = new StringWriter())
{
XmlSerializer xs = new XmlSerializer(typeof(T));
xs.Serialize(sw, Obj);
strxml = sw.ToString();
}
return strxml;
}
Link contains above function, pass your datatable to this function, check out the generated XML and use same casing in stored procedure for elements in XML as XML is case sensitive.