I've got a DataTable which has a number of records.
I've tried the following code for iterating through the DataTable and inserting into the database, but I am receiving an error message saying that the parameters have not been declared:
using (SqlCommand command = new SqlCommand(("My insert statement"), connection))
{
SqlParameter param1 = new SqlParameter();
SqlParameter param2 = new SqlParameter();
param1.ParameterName = "#ProductID";
param2.ParameterName = "#ID";
foreach (DataRow row in table.Rows)
{
param1.Value = row[0].ToString();
param2.Value = row[1].ToString();
command.ExecuteNonQuery();
}
}
Any ideas? Please do not suggest stored procedures - I'd like to do this through this method or something similar.
You need to add your parameters to the command.
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.parameters.aspx
Caution though
The way you have your code if you put the add in the foreach your first execute will work and then the next will fail with an error message stating that you have a variable named #ProductID / #ID already.
You need to clear the parameters each time.
See this for an example.
Check out SqlBulkCopy, it will allow you to pass in your Datatable straight to the database. Link
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName = "dbo.TableYouWantToInsertTheRowsTo";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(newProductsDataTable);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
If you are using SQL2008 than entire List of object/datatable can be passed as parameter to store procedure. In this case you need to declare same class in sql also.
Related
I'm using C# and SQL Server in Windows environment with Visual Studio 2017. I'm trying to pass a datatable (called #profiles) to the SQL script.
In order to do so, I first must create a type of a table that matches the data table passed.
Problem is that in every way I tried to populate a new table with the datatable passed I'm getting one of two exceptions:
"Column, parameter, or variable #profiles. : Cannot find data type ProfileIdTableType."
"The table type parameter '#profiles' must have a valid type name."
From what I searched I could find that a datatable with new table type is generally used with a procedure, but no matter - I still get the above exceptions.
I tried to declare a new table type and use the #profiles with it with no success.
When I declare the SqlParameter I'm using to pass it, I generally encounter the first exception (can't find the type)
I should mention that I can't find the created type in the "Programmability" section of SQL Server (but my type is temp and so it should be)
These are 2 ways I'm using to pass the datatable to the script from C#:
SqlParameter #profiles = new SqlParameter("#profiles", profileIds.Tables[0]);
profiles.TypeName = "ProfileIdTableType";
or:
DbParameter #profiles = new SqlParameter("#profiles", profileIds.Tables[0]);
and then use it:
updatedProfiles = (int)DbAdminOps.ExecuteNonQueryCommand(updateProfileSettingsCommand, CommandType.Text, new DbParameter[] { #profiles, #updatedTemplate }, null);
This is the SQL script i used last (but tried many variations not presented here)
-- create a table type of profile Ids passed by user
CREATE TYPE ProfileIdTableType AS TABLE (ID INT)
go
DECLARE #PRFL ProfileIdTableType
GO
CREATE PROCEDURE PopulateTable
#profiles ProfileIdTableType READONLY
AS
INSERT INTO #PRFL(ID)
SELECT [ID] FROM #profiles
GO
#profiles ProfileIdTableType
EXEC PopulateTable #profiles
go
I expected #profiles be recognized as a table so i can use it in my script but all i get really is exception. I put a lot of effort into it but just couldn't.
Went through all the stack overflow questions, youtubes, microsoft documentation and web.
If there's any information I left out and is important - let me know.
Would really appreciate some advice.
Cheers!
The key point is to specify SqlDbType as Structured plus to define TypeName as shown in the following snippet.
comm.Parameters.AddWithValue("#tvpEmails", dt);
// EMAIL.TVP_Emails should exist on your SQL instance under UDDT types
comm.Parameters[comm.Parameters.Count - 1].TypeName = "EMAIL.TVP_Emails";
comm.Parameters[comm.Parameters.Count - 1].SqlDbType = SqlDbType.Structured;
See the complete code down below. Please let me know if you have any difficulties.
using System.Data;
using System.Data.SqlClient;
using System.Net.Mail;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
var mm = new MailMessage();
using (var conn = new SqlConnection("your connection string"))
{
using (var comm = new SqlCommand())
{
comm.Connection = conn;
conn.Open();
comm.CommandText =
#"INSERT INTO [EMail].[MailAttachments] (fileName,fileSize,attachment)
SELECT fileName, fileSize, attachment FROM #tvpEmails";
var dt = CreateTable();
foreach (var eml in mm.Attachments)
{
var newRow = dt.NewRow();
newRow["FileName"] = eml.Name;
newRow["FileSize"] = eml.ContentStream.Length;
var allBytes = new byte[eml.ContentStream.Length];
newRow["Attachment"] = allBytes;
eml.ContentStream.Position = 0;
dt.Rows.Add(newRow);
}
comm.Parameters.AddWithValue("#tvpEmails", dt);
comm.Parameters[comm.Parameters.Count - 1].TypeName = "EMAIL.TVP_Emails";
comm.Parameters[comm.Parameters.Count - 1].SqlDbType = SqlDbType.Structured;
comm.ExecuteNonQuery();
if (conn.State == ConnectionState.Open)
conn.Close();
}
}
}
private static DataTable CreateTable()
{
var dt = new DataTable();
dt.Columns.Add("FileName", typeof(string));
dt.Columns.Add("FileSize", typeof(long));
dt.Columns.Add("Attachment", typeof(byte[]));
return dt;
}
}
}
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
}
Can anyone share how to coding copy a datatable to another datatable in faster way for C# sqlite? Thanks.
And also need to change column name also. i tried to manually select and update, but encountered error. Appreciate for your sharing. Thanks.
or how can i amend the column header when display datatable in datagridview? Thanks.
dbConnect = new SQLiteConnection("Data Source=school.db;Version=3;");
dbConnect.Open();
cmd4 = new SQLiteCommand();
cmd4 = dbConnect.CreateCommand();
cmd4.CommandText = "DELETE FROM GroupEven";
cmd4.ExecuteNonQuery();
cmd4.CommandText = "SELECT Day, Day_ID, Standard, Timeslot1_TeacherName, Timeslot1_Subject, Timeslot2_TeacherName, Timeslot2_Subject, Timeslot3_TeacherName, Timeslot3_Subject, Timeslot4_TeacherName, Timeslot4_Subject, Timeslot5_TeacherName, Timeslot5_Subject, Timeslot6, Timeslot7_TeacherName, Timeslot7_Subject, Timeslot8_TeacherName, Timeslot8_Subject, Timeslot9_TeacherName, Timeslot9_Subject, Timeslot10_TeacherName, Timeslot10_Subject, Timeslot11_TeacherName, Timeslot11_Subject FROM TimetableFinal";
DataTable dt4 = new DataTable();
SQLiteDataAdapter da4 = new SQLiteDataAdapter(cmd4);
da4.Fill(dt4);
foreach (DataRow dr4 in dt4.Rows)
{
cmd4.CommandText = "INSERT INTO TimetableFinal2 (Day, Day_ID, Standard, 7:30am-8:00am, 7.30am-8.00am, 8:00am-8:30am, 8.00am-8.30am, 8:30am-9:00am, 8.30am-9.00am, 9:00am-9:30am, 9.00am-9.30am, 9:30am-10:00am, 9.30am-10.00am, 10:00am-10:20am, 10:20am-10:50am, 10.20am-10.50am, 10:50am-11:20am, 10.50am-11.20am, 11:20am-11:50am, 11.20am-11.50am, 11:50am-12:20pm, 11.50am-12.20pm, 12:20pm-12:50pm, 12.20pm-12.50pm) VALUES (#Day, #Day_ID, #Standard, #7:30am-8:00am, #7.30am-8.00am, #8:00am-8:30am, #8.00am-8.30am, #8:30am-9:00am, #8.30am-9.00am, #9:00am-9:30am, #9.00am-9.30am, #9:30am-10:00am, #9.30am-10.00am, #10:00am-10:20am, #10:20am-10:50am, #10.20am-10.50am, #10:50am-11:20am, #10.50am-11.20am, #11:20am-11:50am, #11.20am-11.50am, #11:50am-12:20pm, #11.50am-12.20pm, #12:20pm-12:50pm, #12.20pm-12.50pm)";
cmd4.Parameters.AddWithValue("#Day", dr4["Day"].ToString());
SQLite does support Joinings Insert statements, something like this.
INSERT INTO 'tablename' ('column1', 'column2')
VALUES
('data1', 'data2'),
('data3', 'data4'),
('data5', 'data6'),
('data7', 'data8');
See this.. http://www.sqlite.org/lang_insert.html
and then execute this in one go. Also, Make sure you do this in transactions and wrap around in using statements
using(var dbConnect = new SQLiteConnection("DataSource=school.db;Version=3;"))
{
dbConnect.Open();
using(var transaction = dbConnect.BeginTransaction())
{
string insertQuery = ...// your insert query
using (var cmd = dbConnect.CreateCommand())
{
cmd.CommandText = insertQuery;
foreach (DataRow dr4 in dt4.Rows)
{
cmd.Parameters.AddWithValue(...);
}
cmd.ExecuteNonQuery()
}
transaction.Commit();
}
}
Regarding your second part: How can i amend the column header when display datatable in datagridview,
thats totally separate, has nothing to do with Sqlite insertions.
After getting the data source you can do something like this
dataGridView1.Columns[i].HeaderText = "My New header";
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.
Here is my stored procedure:
[dbo].[DFW_Completed_Safety] (
#StartDate VARCHAR(10),
#Station VARCHAR(50),
#EmployeeID INT)
When I code the following:
SqlDataAdapter daAC_CSM = new SqlDataAdapter();
DataSet dsAC_CSM = new DataSet();
try
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlCmd = new SqlCommand();
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Connection = sqlConnection;
sqlCmd.CommandTimeout = 0;
sqlCmd.CommandText = "DFW_Completed_Safety";
sqlCmd.Parameters.AddWithValue("#StartDate", startdate);
sqlCmd.Parameters.AddWithValue("#Station", station);
sqlCmd.Parameters.AddWithValue("#EmployeeID", "0");
daAC_CSM.SelectCommand = sqlCmd;
daAC_CSM.Fill(dsAC_CSM);
}
return dsAC_CSM;
}
catch (Exception)
{
throw;
}
it throws the Exception: EmployeeID is received as a varchar.
Conversion failed when converting the varchar value 'd ' to data type int.
Things I tried:
1- Many others post on StackOverflow suggested that Convert.ToInt32(0); would do it. Since 0 is an Int32 by default, this isn't a solution.
2- Changing the method to receive varchar (send "0") and it doesn't work too.
Thanks for any ideas! (would be greater to keep the method signature to Int).
UPDATE: The question isn't answered yet, since changing my stored procedure to varchar didn't make it.. Any ideas?
Please rewrite your code like this:
try
{
sqlCon = new SqlConnection(connectionString);
sqlCmd = new SqlCommand();
sqlCmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter daAC_CSM = new SqlDataAdapter();
DataSet dsAC_CSM = new DataSet();
sqlCmd.Connection = sqlCon;
sqlCmd.CommandTimeout = 0;
sqlCmd.CommandText = "DFW_Completed_Safety";
sqlCmd.Parameters.AddWithValue("#StartDate", startdate); //Using "#"
sqlCmd.Parameters.AddWithValue("#Station", station); //Using "#"
sqlCmd.Parameters.AddWithValue("#EmployeeID", 0); //Using "#"
foreach(SqlParameter p in sqlCmd.Parameters){
//Will print Name, Type and Value
System.Diagnostics.Trace.WriteLine("Name:" + p.ParameterName + "Type: " + p.DbType+" Value: "+p.Value);
}
sqlCon.Open();
daAC_CSM.SelectCommand = sqlCmd;
daAC_CSM.Fill(dsAC_CSM);
sqlCon.Close();
return dsAC_CSM;
}
catch (Exception ex)
{
throw ex;
}
What does it print? What error do you get?
When you run your procedure from SSMS you will most likely get the same error, as the error is most likely derived from the body of your procedure, rather than how you are calling it. If you have a value 'd ' in a column in the table that you're querying from - and you are comparing that column to an integer type, then you will receive that error. Also, a couple of asides:
You should be putting your SqlCommand and SqlConnection instances in a using clause or disposing of them manually since they are IDisposable.
You probably don't want throw ex in your catch block - you probably just want throw. By using throw ex you mess up the stack trace that was available in the original exception.
Finally it wasn't the first line. The FormName is a field that stores the FormID. The programmer that was here before was probably a noob or changed the Column datatype to int, making all queries not to work. Thanks anyways #Matt_Whitfield & #Luxspes. By the way Luxpes, you were right, it was written line 1 even on SSMS, but I did it using the same:
EXEC #return_value = [dbo].[DFW_Completed_Safety]
#StartDate = N'07-18-2012',
#Station = N'YHZ',
#EmployeeID = 0
And by doing Print #SqlStatement, I was able to copy & paste in a new Query and see that it was the Form*Name* that was an Int. Who knew that a Name could be an Int?