Idictionary as a source of SQL insert into - c#

I created Sql Update procedure using c# dynamic object ( here it is 'string,string' dictionary - 'sql_fields_name,value_to_insert' ) which works well.
public static string sp_UpdateDB(int ID, dynamic a, string procName, string connString)// update using stored procedures
{
string resp=string.Empty;
string conn_string = ConfigurationManager.AppSettings.Get(connString);
using (System.Data.SqlClient.SqlConnection conn = new SqlConnection(conn_string))
{
try
{
conn.Open();
SqlCommand cmd = new SqlCommand(procName, conn);
cmd.CommandType = CommandType.StoredProcedure;
// set id of updated record
cmd.Parameters.Add(new SqlParameter("#ID", SqlDbType.Int));
cmd.Parameters["#ID"].Value = ID;
// set all fields and values
var attribList = a.Keys;
foreach (var entry in attribList)
{
cmd.Parameters.Add(new SqlParameter("#" + entry, SqlDbType.NVarChar, -1));
cmd.Parameters["#" + entry].Value = (a[entry] == "") ? (object)System.DBNull.Value : a[entry];
}
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
resp += ex.Message;
}
}
return resp;
}
and Sql server procedure
ALTER PROCEDURE [dbo].[UpdateDB]
-- Add the parameters for the stored procedure here
#ID int = null
,#JSON_Content nvarchar(max)= NULL
,#number nvarchar(max)= NULL
,#email nvarchar(max)= NULL
,#first_name nvarchar(max)= NULL
,#JSON_changes nvarchar(max)= NULL
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE [Intranet].[dbo].[Applications]
SET
[JSON_Content] = ISNULL(#JSON_Content ,[JSON_Content])
,[staff_number] = ISNULL(#staff_number ,[staff_number])
,[staff_email] = ISNULL(#staff_email ,[staff_email])
,[first_name] = ISNULL(#first_name ,[first_name])
,[JSON_changes] = ISNULL(#JSON_changes ,[JSON_changes])
WHERE
[ID]=#ID
END
Now, I would like to make the same with insert into and I am stuck. Can someone turn me to the right direction? I have got only c# version, I can't make it as a sql stored procedure.
Present code:
public static string insertDB(dynamic a, out int id, string tableName, string connString)
{
string resp = "";
string strSQL = "";
id = 0;
try
{
var attribList = a.Keys;
string updateFields = "";
string updateValues = "";
foreach (var entry in attribList)
{
if (updateFields != "")
updateFields += ",";
if (updateValues != "")
updateValues += ",";
updateFields += "[" + entry + "]";
updateValues += "'" + a[entry] + "'";
}
string conn_string = ConfigurationManager.AppSettings.Get(connString);
SqlConnection conn = new SqlConnection(conn_string);
conn.Open();
strSQL = "insert into " + tableName + " ( " + updateFields + " ) VALUES ( " + updateValues + " )";
SqlCommand pushCommand = new SqlCommand(strSQL, conn);
pushCommand.ExecuteNonQuery();
pushCommand.CommandText = "Select ##Identity"; // get the just created record's ID
id = System.Convert.ToInt32(pushCommand.ExecuteScalar());
conn.Close();
}
catch (Exception ex) { resp += ex.ToString()+" id:"+id.ToString() + " " + strSQL; }
return resp;
}

Related

SqlBulkCopy got exception : "Received an invalid column length from the bcp client for colid ."

I have code using SqlBulkCopy to clone a lot of tables, it used to work before, but very weird, recently got exception
Received an invalid column length from the bcp client for colid
I have search this exception and still not solve my problem.
sqlBulkCopy.WriteToServer(reader) will raise this exception if a table has two continous columns which are both of type Char(1) or nvarchar(nn), and both have NULL. Sometime, changing the SqlBulkCopy.BatchSize makes it work, but many times, it will not.
After simplify, I have test case as follow, and it is reproduceable on two servers:
Create a table like below: (tested on SQL Server 2012 SP 4 and SQL Server 2016 SP2)
IF OBJECT_ID('dbo.TestTable', 'U') IS NOT NULL
DROP TABLE dbo.TestTable;
CREATE TABLE [dbo].[TestTable]
(
[value2] [char](1) NULL,
[value1] [char](1) NULL
) ON [PRIMARY]
GO
DECLARE #i int = 0
WHILE #i < 262
BEGIN
SET #i = #i + 1
INSERT INTO [dbo].[TestTable]([value2], [value1])
VALUES (null, null)
END
C# console (.net framework 4.7) code as below
class Program
{
// [change here]
static string sourceConn = #"Server={YourServer};Database={YourDatabase};User ID={userYourName};Password={yourPassword};connect timeout=15";
static void Main(string[] args)
{
CopyTable(sourceConn, sourceConn, "TestTable", "testTableBAK");
Console.ReadLine();
}
static void CopyTable(string sConnSource, string sConnDest, string sTableSource, string sTableDest)
{
if (IsTableExist(sConnDest, sTableDest))
{
RunNonQuerySQL(sConnDest, "DROP TABLE " + sTableDest);
Console.WriteLine($"existing table {sTableDest} dropped");
}
CopySchema(sConnDest, sTableSource, sTableDest);
using (SqlConnection connSource = new SqlConnection(sConnSource))
{
connSource.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = connSource;
cmd.CommandText = "SELECT * FROM " + sTableSource;
// using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sConnDest, SqlBulkCopyOptions.KeepNulls | SqlBulkCopyOptions.KeepIdentity))
using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sConnDest))
{
// sqlBulkCopy.BatchSize = 1380; // this optional setting will work if set value smaller than 1397 for testTable on my new server (SQL server 13.0.5102.14)
// sqlBulkCopy.BatchSize = 261; // this optional setting will work if set value smaller than 261 for testTable on 2 older server (SQL server 11.0.7001)
sqlBulkCopy.DestinationTableName = sTableDest;
SqlDataReader reader = cmd.ExecuteReader();
try
{
// exception here
sqlBulkCopy.WriteToServer(reader);
Console.WriteLine("table copied");
}
catch (SqlException ex)
{
Console.WriteLine(ex.Message);
}
sqlBulkCopy.Close();
}
}
}
static bool IsTableExist(string sConn, string sTableName)
{
bool result = false;
using (SqlConnection conn = new SqlConnection(sConn))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
string[] s = sTableName.Split('.');
if (s.Length > 1)
{
cmd.CommandText = "select count (*) as counter from information_schema.tables where table_name = '" + s[1] + "' and TABLE_SCHEMA='" + s[0] + "'";
}
else
{
cmd.CommandText = "select count (*) as counter from information_schema.tables where table_name = '" + sTableName + "'";
}
cmd.Connection = conn;
var count = Convert.ToInt32(cmd.ExecuteScalar());
result = count > 0;
}
return result;
}
static bool RunNonQuerySQL(string sConn, string sSQL)
{
bool result = false;
using (SqlConnection conn = new SqlConnection(sConn))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sSQL;
cmd.Connection = conn;
var count = cmd.ExecuteNonQuery();
result = true;
}
return result;
}
static public bool CopySchema(string sConn, string sTableSource, string sTableDest)
{
return RunQuerySQL(sConn, "select * into " + sTableDest + " from " + sTableSource + " where 1=2");
}
static public bool RunQuerySQL(string sConn, string sSQL)
{
using (SqlConnection conn = new SqlConnection(sConn))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sSQL;
cmd.Connection = conn;
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
return true;
}
else
{
return false;
}
}
}
}
I just experienced this error.
I tried to insert a string with a lenght of 5 into a table column with a definition of "varchar(4)".
The error message in my case was:
"Received an invalid column length from the bcp client for colid 2".
"Colid 2" refered to the second column of the row (DataRow) that was part of the DataTable which I used as parameter for the call to the SqlBulkCopy.WriteToServer(DataTable table) method.
The solution in my case was to add validation code that checks the lenght of the strings in my input data before trying to call SqlBulkCopy.WriteToServer().

Conversion failed when converting from a character string to uniqueidentifier when inserting value to sql

I have simple SQL table called test which has two column.first column is an TINYINT and second one is a type of UNIQUEIDENTIFIER.
I have created simple method to insert values into "test" table using for loop and its working fine without any errors.But once i try to create string to uniqueidentifier conversion error it will roll back the transaction and delete all previous inserted values in same transaction.
This is the place where conversion happen
strCommand += "INSERT INTO Test(Test, Test2) VALUES(" + i.ToString() + ", '" + (i == 251 ? Guid.NewGuid().ToString().Remove(12, 1) : Guid.NewGuid().ToString()) + "'); ";
Here is the my complete code
private static string TryThisPlease()
{
SqlConnection connection = null;
SqlCommand command = null;
SqlTransaction transaction = null;
string strRet = "OK";
try
{
connection = new SqlConnection(connectionString);
connection.Open();
//starting transaction mode
transaction = connection.BeginTransaction(IsolationLevel.Snapshot);
command = new SqlCommand("Test", connection);
command.CommandType = CommandType.Text;
command.Transaction = transaction;
//for (int i = 255; i < 257; i++)
for (int i = 250; i < 255; i++)
{
string[] strData = new string[] { "", "3" };
string strCommand = "";
//strCommand += "INSERT INTO Test(Test, Test2) VALUES(" + i.ToString() + ", '" + Guid.NewGuid().ToString() + "'); ";
strCommand += "INSERT INTO Test(Test, Test2) VALUES(" + i.ToString() + ", '" + (i == 251 ? Guid.NewGuid().ToString().Remove(12, 1) : Guid.NewGuid().ToString()) + "'); ";
command.CommandText = strCommand;
if (command.Connection.State != ConnectionState.Open)
command.Connection.Open();
try
{
command.ExecuteNonQuery();
}
catch (Exception EX)
{
strRet = "FAIL";
try
{
}
catch (Exception)
{
strRet = "FAIL";
}
}
}
transaction.Commit();
}
catch (Exception EX)
{
transaction.Rollback();
strRet = "FAIL";
}
finally
{
connection.Close();
}
return strRet;
}
Uncommenting the two lines commented and commenting out lines below,another error with same severity happens. Transactions are not rolled back in this scenario
Is there any way to prevent the transaction being rollback or did i miss something in my code ?
if you want previous inserts to be successful, what you have to do is, create and commit the transaction inside the foreach loop, so that each row is considered separate transaction.
using(SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
for (int i = 250; i < 255; i++) {
using(SqlCommand command = new SqlCommand("", connection, trans)) {
command.CommandType = System.Data.CommandType.Text;
using(SqlTransaction trans = connection.BeginTransaction()) {
try {
strCommand = "INSERT INTO Test(Test, Test2) VALUES(" + i.ToString() + ", '" + (i == 251 ? Guid.NewGuid().ToString().Remove(12, 1) : Guid.NewGuid().ToString()) + "'); ";
command.CommandText = strCommand;
command.ExecuteNonQuery();
trans.Commit();
}
catch(Exception e) {
//Handle Error
trans.Rollback();
}
}
}
}
}
But, your command is prone for sql injection attacks. I would suggest you to parametrize the query as given below:
SqlCommand cmd = new SqlCommand(
"INSERT INTO Test(Test, Test2) VALUES(#id1,#id2)", conn);
cmd.Parameters.Add( new SqlParameter(#id1, SqlDbType.Int)).Value = i;
cmd.Parameters.Add( new SqlParameter(#id2, SqlDbType.Guid)).Value = (i == 251 ? Guid.NewGuid().ToString().Remove(12, 1) : Guid.NewGuid().ToString());
UPDATE
If you want to still go with batch transaction, you can consider savepoint for the transaction. Instead of rolling back the whole transaction, you can rollback till the savepoint.Read more on Savepoint
command.CommandText = strCommand;
trans.Save($"save{i}");
command.ExecuteNonQuery();
trans.Commit();
}
catch(Exception e) {
//Handle Error
trans.Rollback($"save{i}");
trans.Commit();
}
The problem lies in this statement Guid.NewGuid().ToString().Remove(12, 1). The result of this statement will remove the 12th character from your generated GUID which is not a valid GUID and hence the database insertion fails.
Guid Format:
"00000000-0000-0000-0000-000000000000"
^ 12th index character which will get removed from the Guid.
When the condition i==251 becomes true this code Guid.NewGuid().ToString().Remove(12, 1) will get executed and it will generate the error. You need to update this to produce GUID in correct format inorder to solve your issue.

Issue with Open DataReader which must be closed first

I am getting the following error in my code:
"There is already an open DataReader associated with this Command
which must be closed first."
I have two SqlDataReaders and I made sure that I closed the first one after loading the DataViewGrid.
Below is the function that is giving me the issue. I marked the line that is throwing the error. I've tried variations with 'try' and 'using', I've tried to rename diff SqlConnections, SqlDataReaders and SqlCommands. I am at a loss here.
Can I not have an open SqlDataReader and SqlCommand open at the same time on one connection?
private void ApprovedTransferAction(int rowNum) {
bool foundFlag = false;
//int XferQty = (int)gridData.Rows[rowNum].Cells["FinalQty"].Value;
string PorgID = "";
using (SqlConnection conn = new SqlConnection(Global.connString)) {
conn.Open();
// Locate id in PorgReqs
string sqlSelectQuery = "SELECT id FROM PorgReqs WHERE location_id = #NewLocationID AND vendor_id = #VendorID AND item_id = #Item";
using (SqlCommand sqlSelect = new SqlCommand(sqlSelectQuery, conn)) {
sqlSelect.Parameters.Add("#NewLocationID", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["NewLocation"].Value;
sqlSelect.Parameters.Add("#VendorID", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Vendor"].Value;
sqlSelect.Parameters.Add("#Item", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Item"].Value;
using (SqlDataReader sqlDataReader2 = sqlSelect.ExecuteReader()) {
// If Item is found at Target Location; FinalQty of Source Added to AddlQty of Target
if (sqlDataReader2.HasRows) {
sqlDataReader2.Read();
PorgID = Convert.ToString(sqlDataReader2["id"]);
MessageBox.Show("Found ID: " + PorgID);
string sqlUpdateQuery = "UPDATE PorgReqs SET AddlQty += #XferQty WHERE id = #ID";
using (SqlCommand sqlUpdate = new SqlCommand(sqlUpdateQuery, conn)) {
sqlUpdate.Parameters.Add("#XferQty", SqlDbType.Int).Value = (int)gridData.Rows[rowNum].Cells["FinalQty"].Value;
sqlUpdate.Parameters.Add("#ID", SqlDbType.Int).Value = sqlDataReader2["id"];
sqlUpdate.CommandType = CommandType.Text;
sqlUpdate.ExecuteNonQuery();
} // End sqlUpdate Command
} else { // Item was not found at Target location
string sqlUpdateQuery = "UPDATE PorgReqs SET " +
" location_id = #TargetLoc, " +
" requirement_location_id = #TargetLoc, " +
" ship_to_location_id = #TargetLoc " +
" WHERE " +
" location_id = #SourceLoc AND " +
" vendor_id = #VendorID AND " +
" item_id = #Item";
using (SqlCommand sqlUpdate = new SqlCommand(sqlUpdateQuery, conn)) {
sqlUpdate.Parameters.Add("#TargetLoc", SqlDbType.Int).Value = gridData.Rows[rowNum].Cells["NewLocation"].Value;
sqlUpdate.Parameters.Add("#SourceLoc", SqlDbType.Int).Value = gridData.Rows[rowNum].Cells["Location"].Value;
sqlUpdate.Parameters.Add("#VendorID", SqlDbType.Int).Value = gridData.Rows[rowNum].Cells["Vendor"].Value;
sqlUpdate.Parameters.Add("#Item", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Item"].Value;
sqlUpdate.CommandType = CommandType.Text;
sqlUpdate.ExecuteNonQuery(); // ERROR HERE
} // End sqlUpdate Command
} // End Else
sqlDataReader2.Close();
}
/*} catch (Exception ex) {
Console.WriteLine(ex.Message);
MessageBox.Show("Try SQLReader: " + ex.Message);
} */
} // End sqlSelect Command
} // End SQL Connection
// See if id exists in grid
MessageBox.Show("Checking Grid");
foreach (DataGridViewRow row in gridData.Rows) {
if (foundFlag == true)
break;
else if (row.Cells["id"].Value.ToString() == PorgID) {
// Update grid
row.Cells["AddlQty"].Value = Convert.ToInt32(gridData.Rows[rowNum].Cells["FinalQty"].Value) + Convert.ToInt32(row.Cells["AddlQty"].Value);
row.Cells["FinalQty"].Value = Convert.ToInt32(row.Cells["RecQty"].Value) + Convert.ToInt32(row.Cells["AddlQty"].Value);
foundFlag = true;
MessageBox.Show("Found: " + foundFlag);
} // End If
} // End ForEach
// Remove the Row from the Grid
gridData.Rows.RemoveAt(rowNum);
}
Just add MultipleActiveResultSets=true to your connection string.

Using function to return sqldatareader or string[] array

I simply would like a function that returns records from table passing parameters - field names and query. I am using sqldatareader() however the problem is if the results return null it does not work.
So the end result should be for example: (which works except if it returns null or empty)
SqlDataReader user = bw.fetchReader("Firstname, Surname", "Staff");
string firstname = user["Firstname"];
string surname = user["Surname"];
As an alternative can I use string[] array instead? I would rather use string[] array for example:
string[] user = bw.fetchReader("Firstname, Surname", "Staff");
string firstname = user["Firstname"];
string surname = user["Surname"];
This is the code:
public SqlDataReader fetchReader(string fields, string table, string where = null, int count = 0)
{
string strDBConn = db.getDBstring(Globals.booDebug);
SqlConnection conn = new SqlConnection(strDBConn);
using (SqlCommand cmd = new SqlCommand())
{
string whereClause = where != null ? " WHERE " + where : "";
string countRows = count != 0 ? "count(*) as " + fields : fields;
cmd.CommandText = "SELECT " + countRows + " FROM " + table + whereClause;
//HttpContext.Current.Response.Write(cmd.CommandText + "<br>");
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
if(reader.Read())
{
return reader;
}
else
{
return null;
}
}
}
Any help appreciated!

Insert records using ExecuteNonQuery, showing exception that invalid column name

I am using SQL Server 2008.
I want to insert records into a table using ExecuteNonQuery, for that I have written:
customUtility.ExecuteNonQuery("insert into furniture_ProductAccessories(Product_id, Accessories_id, SkuNo, Description1, Price, Discount) values(" + prodid + "," + strAcc + "," + txtSKUNo.Text + "," + txtAccDesc.Text + "," + txtAccPrices.Text + "," + txtAccDiscount.Text + ")");
& following is ExecuteNonQuery function:
public static bool ExecuteNonQuery(string SQL)
{
bool retVal = false;
using (SqlConnection con = new SqlConnection(System.Web.Configuration.WebConfigurationManager.ConnectionStrings["dbConnect"].ToString()))
{
con.Open();
SqlTransaction trans = con.BeginTransaction();
SqlCommand command = new SqlCommand(SQL, con, trans);
try
{
command.ExecuteNonQuery();
trans.Commit();
retVal = true;
}
catch(Exception ex)
{
//HttpContext.Current.Response.Write(SQL + "<br>" + ex.Message);
//HttpContext.Current.Response.End();
}
finally
{
// Always call Close when done reading.
con.Close();
}
return retVal;
}
}
But it's showing exception that invalid column name to Description1 and even it's value which coming from txtAccDesc.Text. I have tried by removing Description1 column, other records are getting inserted successfully.
My psychic debugging powers are telling me you're entering in the value Description1 into the textbox txtAccDesc. When you concatenated the SQL string You failed to delimit the literal value.
e.g.
"," + txtAccDesc.Text + "," +
Should be
", '" + txtAccDesc.Text + "', " +
However this is a bad solution because it opens you up to SQL injection attacks (not to mention you'd need to deal with quotes and commas in your literals) you should use parametrized queries instead.
e.g. (Warning written in notepad and may not compile)
string SQL = "insert into furniture_ProductAccessories(Product_id,Accessories_id,SkuNo,Description1,Price,Discount) values(#Product_id,#Accessories_id,#SkuNo,#Description1,#Price,#Discount)"
SqlParameters[] parameters = new SQLParameters[6];
parameters[0] = new SqlParameter("#Product_id", SqlDbType.Int, prodid );
parameters[1] = new SqlParameter("#Accessories_id", SqlDbType.VarChar, strAcc );
parameters[2] = new SqlParameter("#SkuNo", SqlDbType.VarChar, txtSKUNo);
parameters[3] = new SqlParameter("#Description1", SqlDbType.VarChar, txtAccDesc.Text);
parameters[4] = new SqlParameter("#Price", SqlDbType.Money, txtAccPrices.Text);
parameters[5] = new SqlParameter("#Discount", SqlDbType.Money, txtAccDiscount.Text);
customUtility.ExecuteNonQuery(sql, paramters)
public static bool ExecuteNonQuery(string SQL, SqlParameters[] parameters)
{
bool retVal = false;
using (SqlConnection con = new SqlConnection(System.Web.Configuration.WebConfigurationManager.ConnectionStrings["dbConnect"].ToString()))
{
con.Open();
SqlTransaction trans = con.BeginTransaction();
SqlCommand command = new SqlCommand(SQL, con, trans);
cmd.parameters.AddRange(parameters);
try
{
command.ExecuteNonQuery();
trans.Commit();
retVal = true;
}
catch(Exception ex)
{
//HttpContext.Current.Response.Write(SQL + "<br>" + ex.Message);
//HttpContext.Current.Response.End();
}
// finally
//{
//Always call Close when done reading.
//con.Close(); Using already does this, so need for this
//}
return retVal;
}
}

Categories