This question already has answers here:
Passing a varchar full of comma delimited values to a SQL Server IN function
(27 answers)
Closed 5 years ago.
I have a stored procedure that uses the IN statement in the select condition.
SELECT *
FROM vwCashTransactions
WHERE TransactionTimeStamp BETWEEN '2017-01-30 ' AND '2017-12-01'
AND Country IN ('MY', 'BD')
ORDER BY TransactionTimeStamp DESC
I need to pass the country string from backend code.
This is the code I have written
if (manageCountries != null && manageCountries.Trim().Length > 0)
{
string[] words = manageCountries.Split(',');
string queryManageString = "";
int i = 0;
foreach (string word in words)
{
if (i != 0)
{
queryManageString += "','";
}
i++;
queryManageString += "'" + word + "'";
}
_DataTable = Global.DatabaseServices.GetTransactionReport("", startPeriod, endPeriod, queryManageString);
Somehow I am not getting the values. I am sure the issue is with the querymanageString. The way it is built is missing something. Can someone give an idea how I can achieve it?
Here's the code for calling the database:
public DataTable GetTransactionReport(string AccountCode, DateTime FromDate, DateTime ToDate, string ManagedCountry)
{
DataTable dataTable = new DataTable();
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandText = "[GetTransactionReport]";
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.AddWithValue("#AccountCode", AccountCode);
sqlCommand.Parameters.AddWithValue("#FromDate", FromDate);
sqlCommand.Parameters.AddWithValue("#ToDate", ToDate);
sqlCommand.Parameters.AddWithValue("#ManagedCountry", ManagedCountry);
sqlCommand.CommandTimeout = 300;
ExecuteQuery(dataTable, sqlCommand);
sqlCommand.Dispose();
return dataTable;
}
public int ExecuteQuery(DataTable dt, SqlCommand cmd)
{
int rowCount = 0;
SqlDataAdapter da = null;
try
{
if (cmd.Connection == null)
cmd.Connection = GetSqlConnection();
da = new SqlDataAdapter();
da.SelectCommand = cmd;
rowCount = da.Fill(dt);
}
catch (Exception ex)
{
throw new DatabaseException(ex);
}
finally
{
cmd.Connection.Close();
cmd.Connection.Dispose();
cmd.Connection = null;
da.Dispose();
}
return rowCount;
}
It's not very clear how you pass the parameters, but it seems that you pass a delimited string. This will not work. Your procedure needs a list of country ids, not a string with a delimiter.
You can either do some magic in the stored procedure, splitting string and stuff like that, or create your own type.
Try something like this:
CREATE TYPE [dbo].[StringList] AS TABLE
([StringValue] [varchar](200) NULL)
Then your stored procedure has a parameter of type StringList, which can be used just like a normal table:
ALTER PROCEDURE [dbo].[MySproc]
#ids AS dbo.StringList READONLY
AS
BEGIN
SET NOCOUNT ON;
...etc..
And, finally, in your code use a DataTable for the values:
DataTable idsDT = new DataTable();
idsDT.Columns.Add("StringValue", typeof(string));
// fill datatable here
And the command parameter should be SqlDbType.Structured
var cmd = new SqlCommand(....)
SqlParameter countryParam = cmd.Parameter.AddWithValue("ids", idsDT);
countryParam.SqlDbType = SqlDbType.Structured;
It seems, there is something wrong in your for loop where you create comma seperated single quote string. Update for loop with below:
string[] words = manageCountries.Split(',');
string queryManageString = "";
int i = 0;
foreach (string word in words)
{
if (i != 0)
{
queryManageString += ",'" + word + "'";
}
else
{
queryManageString += "'" + word + "'";
}
i++;
}
OR if you don't want to go with for loop, here is one line solution
queryManageString = string.Join(",", words.Select(x => string.Format("'{0}'", x)));
Related
I am trying to implement an ADO.NET code which executes the SQL query with multiple parameters. Looks like SQL parameter limit is 2100 and does not accept more than this limit. How do I achieve with my below code to have this accept more than the limitation.
I am finding it difficult to understand the implementations when validating online articles related how to send the queries in subsets or chunks to fulfill my request.
This is my code:
using (Connection = new SqlConnection(CS))
{
Connection.Open();
string query = "SELECT FamilyID, FullName, Alias FROM TABLE (nolock) WHERE FamilyID IN ({0})";
var stringBuiler = new StringBuilder();
var familyIds = new List<string>();
string line;
while ((line = TextFileReader.ReadLine()) != null)
{
line = line.Trim();
if (!familyIds.Contains(line) & !string.IsNullOrEmpty(line))
{
familyIds.Add(line);
}
}
var sqlCommand = new SqlCommand
{
Connection = Connection,
CommandType = CommandType.Text
};
var index = 0; // Reset the index
var idParameterList = new List<string>();
foreach (var familyId in familyIds)
{
var paramName = "#familyId" + index;
sqlCommand.Parameters.AddWithValue(paramName, familyId);
idParameterList.Add(paramName);
index++;
}
sqlCommand.CommandText = String.Format(query, string.Join(",", idParameterList));
var dt = new DataTable();
using (SqlDataReader sqlReader = sqlCommand.ExecuteReader())
{
dt.Load(sqlReader);
}
try
{
if (dt.Rows.Count > 0)
{
OutputdataGridView.DataSource = lstDownloadOwnerOutput;
OutputdataGridView.ColumnHeadersDefaultCellStyle.Font = new Font(DataGridView.DefaultFont, FontStyle.Bold);
OutputdataGridView.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
Gridviewdisplaylabel.Text = "Total no of rows: " + this.OutputdataGridView.Rows.Count.ToString();
}
else if (dt.Rows.Count == 0)
{
MessageBox.Show("Data returned blank!!!");
}
}
catch (Exception Ex)
{
if (Connection != null)
{
Connection.Close();
}
MessageBox.Show(Ex.Message);
}
}
Having a WHERE IN clause with 2100, or even 100, parameters is generally not good coding practice. You might want to consider putting those values into a separate bona fide table, e.g.
families (ID int PK, ...)
Then, you may rewrite your query as:
SELECT FamilyID, FullName, Alias
FROM TABLE (nolock)
WHERE FamilyID IN (SELECT ID FROM families);
You could also express the above using an EXISTS clause or a join, but all three approaches might just optimize to a very similar query plan anyway.
You can just add a table load call every 2000 parameters in your code:
var index = 0; // Reset the index
var idParameterList = new List<string>();
var dt = new DataTable();
foreach (var familyId in familyIds) {
var paramName = "#familyId" + index;
sqlCommand.Parameters.AddWithValue(paramName, familyId);
idParameterList.Add(paramName);
index++;
if (index > 2000) {
sqlCommand.CommandText = String.Format(query, string.Join(",", idParameterList));
using (SqlDataReader sqlReader = sqlCommand.ExecuteReader())
dt.Load(sqlReader);
sqlCommand.Parameters.Clear();
idParameterList.Clear();
index = 0;
}
}
if (index > 0) {
sqlCommand.CommandText = String.Format(query, string.Join(",", idParameterList));
using (SqlDataReader sqlReader = sqlCommand.ExecuteReader())
dt.Load(sqlReader);
}
For dynamic sql like this, I generally recommend using a Table-Valued Parameter.
It does require a bit of setup: you have to create a user-defined Type in the DB to hold the values, but that is a fairly trivial operation:
CREATE TYPE PrimaryKeyType AS TABLE ( VALUE INT NOT NULL );
We generally use these in conjunction with stored procedures:
CREATE PROCEDURE dbo.getFamily(#PrimaryKeys PrimaryKeyType READONLY)
AS
SELECT FamilyID, FullName, Alias
FROM TABLE (nolock) INNER JOIN #PrimaryKeys ON TABLE.FamilyID = #PrimaryKeys.Value
GO
However, you can also use inline SQL if you prefer.
Assigning the values to the stored proc or inline parameter is fairly straightforward, but there is one gotcha (more later):
public static void AssignValuesToPKTableTypeParameter(DbParameter parameter, ICollection<int> primaryKeys)
{
// Exceptions are handled by the caller
var sqlParameter = parameter as SqlParameter;
if (sqlParameter != null && sqlParameter.SqlDbType == SqlDbType.Structured)
{
// The type name may look like DatabaseName.dbo.PrimaryKeyType,
// so remove the database name if it is present
var parts = sqlParameter.TypeName.Split('.');
if (parts.Length == 3)
{
sqlParameter.TypeName = parts[1] + "." + parts[2];
}
}
if (primaryKeys == null)
{
primaryKeys = new List<int>();
}
var table = new DataTable();
table.Columns.Add("Value", typeof(int));
foreach (var wPrimaryKey in primaryKeys)
{
table.Rows.Add(wPrimaryKey);
}
parameter.Value = table;
}
The thing to watch out for here is the naming of the parameter. See the code in the method above that removes the database name to resolve this issue.
If you have dynamic SQL, you can generate a correct parameter using the following method:
public static SqlParameter CreateTableValuedParameter(string typeName, string parameterName)
{
// Exceptions are handled by the caller
var oParameter = new SqlParameter();
oParameter.ParameterName = parameterName;
oParameter.SqlDbType = SqlDbType.Structured;
oParameter.TypeName = typeName;
return oParameter;
}
Where typeName is the name of your type in the DB.
I am new to coding and looking for some help on how to pass multiple values to a single parameter in an inline SQL query. I have framed the below query, but I heard this could result in SQL-injection issue. Kindly help on how can I frame the below by using parameter based in the SQL query.
string query = "Select ID, email FROM DBTABLE WHERE email in (";
var stringBuiler = new StringBuilder();
using (StringReader stringReader = new StringReader(DownloadIDtextBox.Text))
{
string line;
string prefix = "";
while ((line = stringReader.ReadLine()) != null)
{
stringBuiler.Append(prefix);
prefix = ",";
stringBuiler.Append("'" + line + "'");
}
}
query += stringBuiler.ToString() + ")";
SqlDataAdapter da = new SqlDataAdapter(query, Connection);
DataTable dt = new DataTable();
da.Fill(dt);
Just want to mention that ID is GUID format.
If you are doing it manually, the process would be (basically):
var stringBuiler = new StringBuilder("Select ID, email FROM DBTABLE WHERE email in (");
// create "cmd" as a DB-provider-specific DbCommand instance, with "using"
using (...your reader...)
{
int idx = 0;
...
while ((line = stringReader.ReadLine()) != null)
{
// ...
Guid val = Guid.Parse(line);
// ...
var p = cmd.CreateParameter();
p.Name = "#p" + idx;
p.Value = val;
if (idx != 0) stringBuiler.Append(",");
stringBuiler.Append(p.Name);
cmd.Parameters.Add(cmd);
idx++;
}
}
cmd.CommandText = stringBuiler.Append(")").ToString();
and use that... meaning: you don't use inline SQL - you use fully parameterized SQL. There are tools in the ORM/micro-ORM families that will help immensely here, though - making it a one-liner.
I have found numerous posts on this but I haven't been able to make any
of them work. The code below is the closest I have come to
making it work. I read out the values for the ddlSIPA listbox below and
the result looks correct but it seems SQL server isn't handling the
IN statement for the listbox items.
public void LoadChecklist(Object sender, EventArgs e)
{
System.Data.DataTable SearchResultsTable = new System.Data.DataTable();
SqlCommand cmd = new SqlCommand("sp_get_QUADRA_CHECKLIST", conn);
cmd.CommandType = CommandType.StoredProcedure;
//create sql adapter by passing command object
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
SearchResultsTable.Clear();
string strYourIDs = "";
int[] yourSelectedIndexes = ddlSIPA.GetSelectedIndices();
for (int i = yourSelectedIndexes.Length - 1; i >= 0; i--)
{
strYourIDs += "'" + ddlSIPA.Items[yourSelectedIndexes[i]].Value + "',";
}
if (strYourIDs != "")
strYourIDs = strYourIDs.TrimEnd(",".ToCharArray());
try
{
cmd.Parameters.AddWithValue("#SIPA", strYourIDs);
Response.Write(strYourIDs);
cmd.Parameters.AddWithValue("#AP_DEV", CbAPDev.Checked);
cmd.Parameters.AddWithValue("#PROD_DEV", cbProdDev.Checked);
cmd.Parameters.AddWithValue("#ROTYPE", ddlROTYPE.SelectedItem.Value);
adapter.Fill(SearchResultsTable);
if (SearchResultsTable.Rows.Count > 0)
{
//SearchResultsTable.ToString();
GV1.DataSource = SearchResultsTable;
GV1.DataBind();
}
else if (SearchResultsTable.Rows.Count == 0)
{
//Response.Write("No records found!");
ScriptManager.RegisterStartupScript(this, GetType(), "showalert", "alert('No records found!');", true);
GV1.DataBind();
}
}
catch (System.Data.SqlClient.SqlException ex)
{
Response.Write(ex);
}
finally
{
conn.Close();
}
}
SQL Query:
USE [VISIBILITY_BOARD]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[sp_get_QUADRA_CHECKLIST] (
#AP_DEV bit ''
,#PROD_DEV bit = ''
,#ROTYPE nvarchar(255) = ''
,#SIPA nvarchar(255) = '') AS
--,#RO nvarchar(255) = '') AS
SELECT h.QUES_ANSWER
, h.COMMENTS
, cl.RO_TYPE
, cl.RO
, cl.QUES_ID
, cl.DFQRO AS QDRO
, cl.QUADRA_QUES
FROM Tbl_QUADRA_CL cl
LEFT JOIN TBL_QUADRA_ASSMNT_HIST h
ON cl.QUES_ID = h.QUES_ID
WHERE (cl.RO_TYPE = #ROTYPE OR #ROTYPE IS NULL)
AND (cl.SIPA IN (#SIPA) OR #SIPA IS NULL)
AND (cl.AP_DEV = #AP_DEV OR #AP_DEV IS NULL)
AND (cl.PROD_DEV = #PROD_DEV or #PROD_DEV IS NULL)
GROUP BY h.QUES_ANSWER
, h.COMMENTS
, cl.RO_TYPE
, cl.RO
, cl.QUES_ID
, cl.DFQRO
, cl.QUADRA_QUES
SET QUOTED_IDENTIFIER On
GO
Here is your problem: AND (cl.SIPA IN (#SIPA) OR #SIPA IS NULL)
You are making a very common mistake - The IN operator expects a list of values separated by a comma, but you are giving it a single value that happens to contain a comma-separated list.
Since you are using c# and sql-server, I would advise to use a table valued parameter instead.
Please note that there are also some other problems in your code:
Using a class level SQLConnection - That's a mistake. A correct use of SQLConnection would be as a local variable inside a using statement.
Using instances of classes that implements the IDisposable interface and not disposing them - SQLCommand and SQLDataAdapter in your case.
Using AddWithValue - Read Can we stop using AddWithValue() already? for details.
A better c# code would look more like this:
public void LoadChecklist(Object sender, EventArgs e)
{
var SearchResultsTable = new DataTable();
using (var con = new SqlConnection("<ConnectionStringGoesHere>"))
{
using (var cmd = new SqlCommand("sp_get_QUADRA_CHECKLIST", con))
{
cmd.CommandType = CommandType.StoredProcedure;
using(var adapter = new SqlDataAdapter(cmd))
{
using(var dtSIPA = new DataTable())
{
dtSIPA.Columns.Add("Id", typeof(int)); -- assuming you are looking for a list of int values
int[] yourSelectedIndexes = ddlSIPA.GetSelectedIndices();
for (int i = yourSelectedIndexes.Length - 1; i >= 0; i--)
{
dtSIPA.Rows.Add(ddlSIPA.Items[yourSelectedIndexes[i]].Value);
}
cmd.Parameters.Add("#AP_DEV", SqlDbType.Bit).Value = CbAPDev.Checked;
cmd.Parameters.Add("#PROD_DEV", SqlDbType.Bit).Value = cbProdDev.Checked;
cmd.Parameters.Add("#ROTYPE", SqlDbType.NVarChar, 255).Value = ddlROTYPE.SelectedItem.Value;
cmd.Parameters.Add("#SIPA", SqlDbType.Structured).Value = dtSIPA;
}
try
{
adapter.Fill(SearchResultsTable);
}
catch (System.Data.SqlClient.SqlException ex)
{
Response.Write(ex);
}
}
}
}
}
As for your stored procedure, you need to create a user defined table type for the #SIPA parameter:
CREATE TYPE SIPA AS TABLE
(
Id int
)
and change the condition to AND (cl.SIPA IN (SELECT Id FROM #SIPA) OR (SELECT COUNT(*) FROM #SIPA) = 0)
how to split into a string array and pass them to command parameters or hiddenfield, just need to split the string "S0010M,AZI002M,3,12/26/2013 12:00:00 AM,VDIQ20"
to pass with parameters like
cmd.Parameters.AddWithValue("#DealerCode", "S0010M");
cmd.Parameters.AddWithValue("#Code", "AZI002M");
cmd.Parameters.AddWithValue("#Qty", 33);
cmd.Parameters.AddWithValue("#ExpireDate", "12/26/2015");
cmd.Parameters.AddWithValue("#BatchNumber", "VDIQ20");
i have big problem about this .. please can you help me to fix this , beaus still learning the subject..
after click on Return button , take the data from gridview, it can be more than one rows.
protected void btnReturn_Click(object sender, EventArgs e)
{
int rowIndex = 0;
StringCollection SetDEL_Stores = new StringCollection();
if (ViewState["CurrentData"] != null)
{
DataTable dtCurrentTable = (DataTable)ViewState["CurrentData"];
DataRow drCurrentRow = null;
if (dtCurrentTable.Rows.Count > 0)
{
for (int i = 1; i <= dtCurrentTable.Rows.Count; i++)
{
var DealerCode = HFDealerCode.Value;
var ItemIdentityCode = (Label)GridViewSalesReturn.Rows[rowIndex].Cells[2].FindControl("ItemIdentityCode");
var Qty = (Label)GridViewSalesReturn.Rows[rowIndex].Cells[8].FindControl("Quantity");
var ExpireDate = (Label)GridViewSalesReturn.Rows[rowIndex].Cells[6].FindControl("ExpireDate");
var BatchNumber = (Label)GridViewSalesReturn.Rows[rowIndex].Cells[7].FindControl("BatchNumber");
CultureInfo ci = new CultureInfo("en-GB");
SetDEL_Stores.Add(DealerCode + "," + ItemIdentityCode.Text + "," + decimal.Parse(Qty.Text) + "," + DateTime.ParseExact(ExpireDate.Text, "dd/MM/yyyy", CultureInfo.InvariantCulture) + "," + BatchNumber.Text);
rowIndex++;
}
InsertDEL_Stores(SetDEL_Stores);
}
}
}
//in InsertDEL_Stores(SetDEL_Stores); event , taking the stringline separated with "," ,,
private void InsertDEL_Stores(StringCollection SC_PurLinr)
{
String strConnString = ConfigurationManager.ConnectionStrings["CBConnectionString"].ConnectionString;
DataSet ds = new DataSet();
SqlConnection con = new SqlConnection(strConnString);
SqlCommand cmd = new SqlCommand("sp_DEL_Stores_IU", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#DealerCode", SC_PurLinr[0]);
cmd.Parameters.AddWithValue("#Code", SC_PurLinr[1]);
cmd.Parameters.AddWithValue("#Qty", SC_PurLinr[2]);
cmd.Parameters.AddWithValue("#ExpireDate", SC_PurLinr[3]);
cmd.Parameters.AddWithValue("#BatchNumber", SC_PurLinr[4]);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
It is not clear why you need a string collection first. If you want to keep the contents of the single rows in the GridView then start defining a class for your items where every single field is typed correctly (string for strings, numeric for numerics and datetime for dates) Copying the content of the grid in a string collection is just a waste of time and memory because every time you need to use the values stored in the string collection you need to find the correct string and split it to the individual fields.
I could just offer a pseudocode here because I haven't the possibility to test it.
(As an example I have named this class MyItem, but you could call it as you wish)
public class MyItem
{
public string DealerCode;
public string ItemCode;
public int Quantity;
public Datetime ExpireDate;
public string BatchNumber;
}
Then in your loop
// To keep the content of the grid keyed on the BatchNumber field
Dictionary<string, MyItem> items = new Dictionary<string, MyItem>();
for (int rowIndex = 0; i < dtCurrentTable.Rows.Count; i++)
{
MyItem itm = new MyItem();
itm.DealerCode = HFDealerCode.Value.ToString();
itm.ItemCode = GetGridValue(rowIndex, 2, "ItemIdentityCode");
itm.Quantity = Convert.ToDecimal(GetGridValue(rowIndex, 8, "Quantity");
itm.ExpireDate = Convert.ToDateTime(GetGridValue(rowIndex, 6, "ExpireDate");
itm.BatchNumber = GetGridValue(rowIndex, 7, "BatchNumber");
// Add the item to the dictionary for future reuses, however if you just want to store
// the item in the database this line is not needed
items.Add(itm.BatchNumber, itm);
// notice that the storing is executed inside the loop that extracts the values
// so every row is updated/inserted in the database
InsertDEL_Stores(itm);
}
GetGridValue is a method that you should write taking the parameters passed and returning a string with the value searched on the current row of your gridview. This could be simple as
string GetGridValue(int rowIndex, int cellIndex, string controlName)
{
Control c = GridViewSalesReturn.Rows[rowIndex].Cells[cellIndex].FindControl(controlName);
return (c != null ? c.Value.ToString() : "");
}
but you need to test it for its correctness.
However, after that you have an istance of MyItem class that you could store in the dictionary for future reuses or just pass it to the database working procedure
private void InsertDEL_Stores(MyItem itm)
{
String strConnString = ConfigurationManager.ConnectionStrings["CBConnectionString"].ConnectionString;
using(SqlConnection con = new SqlConnection(strConnString))
using(SqlCommand cmd = new SqlCommand("sp_DEL_Stores_IU", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#DealerCode", itm.DealerCode);
cmd.Parameters.AddWithValue("#Code", itm.ItemCode);
cmd.Parameters.AddWithValue("#Qty", itm.Quantity);
cmd.Parameters.AddWithValue("#ExpireDate", itm.ExpireDate);
cmd.Parameters.AddWithValue("#BatchNumber", itm.BatchNumber);
con.Open();
cmd.ExecuteNonQuery();
}
}
I am aware that this code could raise more questions than the one that you try to resolve, neverless I think that this is more OOP than a simple string split
To split a string using commas as the separator character do the following
String[] values = str.split(",");
Then you can access the array in the following way
values[0];
But since your question is a bit confusing I suggest you read well the comments by other contributors what best suits your needs, how you are passing those values to the command parameters. Certainly, dictionaries and lists are more efficient than String collections
I've data in DataTable with 2 rows and 3 columns. I want to insert that data into Oracle table.
How can I insert? please give me with some example.
And also
How can I pass datatable to storedprocedure in ORACLE...
I pass datatable in below mensioned manner, but datatable type problem is comming. how can I solve this?
cmd.Parameters.Add("#Details",dtSupplier);
(OR)
cmd.Parameters.Add("Details", DbType.Single).Value = dtSupplier.ToString();
want to insert dataset or a datatable into ORACLE,
create an ORACLE data adapter.
create a command object for insertion,
set the CommandType to StoredProcedure.
Update command of the data adapter,
pass the dataset or datatable as parameter.
like this:
OracleDataAdapter da = new OracleDataAdapter();
OracleCommand cmdOra = new OracleCommand(StoredProcedureName, Connection);
cmdOra.CommandType = CommandType.StoredProcedure;
da.InsertCommand = cmdOra;
da.Update(dsDataSet);
OR
if above dont work than pass datatable as xml prameter than than process it
For details check : ADO.NET DataTable as XML parameter to an Oracle/SQL Server Database Stored Procedure
OR
Check this thread on Oracle site : Thread: Pass data table to Oracle stored procedure
Check existing answer : How to Pass datatable as input to procedure in C#?
I'm very late for this answer, but I elaborated a bit to have some more readable (I hope) code, and to avoid all those .ToString() for the values so nulls and other less common values can be handled; here it is:
public void Copy(String tableName, DataTable dataTable)
{
var insert = $"insert into {tableName} ({GetColumnNames(dataTable)}) values ({GetParamPlaceholders(dataTable)})";
using (var connection = /*a method to get a new open connection*/)
{
for (var row = 0; row < dataTable.Rows.Count; row++)
{
InsertRow(dataTable, insert, connection, row);
}
}
}
private static void InsertRow(DataTable dataTable, String insert, OracleConnection connection, Int32 row)
{
using (var command = new OracleCommand(insert, connection))
{
AssembleParameters(dataTable, command, row);
command.ExecuteNonQuery();
}
}
private static void AssembleParameters(DataTable dataTable, OracleCommand command, Int32 row)
{
for (var col = 0; col < dataTable.Columns.Count; col++)
{
command.Parameters.Add(ParameterFor(dataTable, row, col));
}
}
private static OracleParameter ParameterFor(DataTable dataTable, Int32 row, Int32 col)
{
return new OracleParameter(GetParamName(dataTable.Columns[col]), dataTable.Rows[row].ItemArray.GetValue(col));
}
private static String GetColumnNames(DataTable data) => (from DataColumn column in data.Columns select column.ColumnName).StringJoin(", ");
private static String GetParamPlaceholders(DataTable data) => (from DataColumn column in data.Columns select GetParamName(column)).StringJoin(", ");
private static String GetParamName(DataColumn column) => $":{column.ColumnName}_param";
Hope this can be still useful to somebody
The best idea would be follow the step mentioned below
Create a transaction
Begin the transaction
Loop through you data table
call your procedure
If no error occurred commit transaction
else roll back transaction
Regarding this part of your question:
cmd.Parameters.Add("#Details",dtSupplier);
(OR)
cmd.Parameters.Add("Details", DbType.Single).Value = dtSupplier.ToString();
What is the type of the "Details" parameter? Is it a Single? Then you would have to pick one (1) value from your DataTable and pass it to your parameter, something like dtSupplier.Rows[0]["col"].
If you use dtSupplier.ToString() you are just making a string of the entire DataTable (which i guess will always be the type name of DataTable).
First of all, you need to add Oracle.DataAccess.dll as reference in Visual Studio. In most cases, you can find this dll in the directory C:\ProgramData\Oracle11g\product\11.2.0\client_1\ODP.NET\bin\2.x\Oracle.DataAccess.dll
If just you need to insert the records from DataTable to Oracle table, then you can call the below function. Consider that your DataTable name is dt.
string error = "";
int noOfInserts = DataTableToTable(dt,out error);
1. Without using Oracle Parameters(special character non-safe)
The definition of the function is given below. Here, we are just making the query dynamic for passing this as a sql statement to the InsertWithQuery function.
public int DataTableToTable(DataTable dt,out string error)
{
error = "";
for (int i = 0; i < dt.Rows.Count; i++)
{
finalSql = "INSERT INTO TABLENAME SELECT ";
for (int j = 0; j < dt.Columns.Count; j++)
{
colValue += "'" + dt.Rows[i][j].ToString() + "',";
}
colValue = colValue.Remove(colValue.Length - 1, 1);
finalSql += colValue + " FROM DUAL";
InsertWithQuery(finalSql, out error);
if (error != "")
return error;
inserts++;
colValue = "";
}
}
The code for InsertWithQuery function is given below. Here, in the connection string you have to place you database details like Host,Username,Password etc.
public int InsertWithQuery(string query, out string error)
{
error = "";
int rowsInserted = 0;
if (error == "")
{
OracleConnection con = new OracleConnection("Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=)(PORT=)))(CONNECT_DATA=(SERVER=DEDICATED)(SID=)));User Id=;Password=");
OracleTransaction trans = con.BeginTransaction();
try
{
error = "";
OracleCommand cmd = new OracleCommand();
cmd.Transaction = trans;
cmd.Connection = con;
cmd.CommandText = query;
rowsInserted = cmd.ExecuteNonQuery();
trans.Commit();
con.Dispose();
return rowsInserted;
}
catch (Exception ex)
{
trans.Rollback();
error = ex.Message;
rowsInserted = 0;
}
finally
{
con.Dispose();
}
}
return rowsInserted;
}
2. With using Oracle Parameters(special character safe)
This can handle special characters like single quotes like scenarios in the column values.
public int DataTableToTable(DataTable dt,out string error)
{
error = "";
string finalSql = "";
List<string> colValue = new List<string>();
List<string> cols = new List<string>() {"COLUMN1","COLUMN2","COLUMN3"};
for (int i = 0; i < dt.Rows.Count; i++)
{
finalSql = "INSERT INTO TABLENAME(COLUMN1,COLUMN2,COLUMN3) VALUES(:COLUMN1,:COLUMN2,:COLUMN3) ";
for (int j = 0; j < dt.Columns.Count; j++)
{
colValue.Add(dt.Rows[i][j].ToString());
}
objDAL.InsertWithParams(finalSql,colValue,cols, out error);
if (error != "")
return error;
inserts++;
colValue.Clear();
}
}
And the InsertWithParams is given below
public string InsertWithParams(string sql, List<string> colValue, List<string> cols, out string error)
{
error = "";
try
{
OracleConnection con = new OracleConnection("Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=)(PORT=)))(CONNECT_DATA=(SERVER=DEDICATED)(SID=)));User Id=;Password=");
OracleCommand command = new OracleCommand(sql, con);
for (int i = 0; i < colValue.Count; i++)
{
command.Parameters.Add(new OracleParameter(cols[i], colValue[i]));
}
command.ExecuteNonQuery();
command.Connection.Close();
}
catch (Exception ex)
{
error = ex.Message;
}
return null;
}
try {
//Suppose you have DataTable dt
string connectionString = #"Provider=Microsoft.ACE.OLEDB.12.0;" +
#"Data Source='Give path of your access database file here';Persist Security Info=False";
OleDbConnection dbConn = new OleDbConnection(connectionString);
dbConn.Open();
using (dbConn)
{
int j = 0;
for (int i = 0; i < 2; i++)
{
OleDbCommand cmd = new OleDbCommand(
"INSERT INTO Participant_Profile ([column1], [column2] , [column3] ) VALUES (#c1 , #c2 , #c3 )", dbConn);
cmd.Parameters.AddWithValue("#c1", dt.rows[i][j].ToString());
cmd.Parameters.AddWithValue("#c2", dt.rows[i][j].ToString());
cmd.Parameters.AddWithValue("#c3", dt.rows[i][j].ToString());
cmd.ExecuteNonQuery();
j++;
}
}
}
catch (OleDbException exception)
{
Console.WriteLine("SQL Error occured: " + exception);
}
I know it's been a big WHILE upon the matter, but the same need: "to insert data from a datatable to an Oracle table" has happened to me. I found this thread. I also tried the answers and came to the conclusion that executing a
...
cmd.ExecuteNonQuery();
...
in a loop, is bad. Reeaaally bad. The first thing that is bad is performance, the second is unnecessary complexity, the third is unnecessary Oracle Objects (stored proc). The time it takes to complete, lets say 200 rows, is almost 1 minute and that's me rounding it down. So in the hope that someone else will find this helpful here's my experience.
I got stubborn and searched some more, so I found out this, true it's from 2018. But I'm in 2021 myself...
So the base code is:
using Oracle.ManagedDataAccess.Client; // you don't need other dll, just install this from nuget gallery
using System.Data;
public static void Datatable2Oracle(string tableName, DataTable dataTable)
{
string connString = "connection string";
OracleBulkCopy copy= new(connString, OracleBulkCopyOptions.UseInternalTransaction /*I don't know what this option does*/);
copy.DestinationTableName = tableName;
copy.WriteToServer(dataTable);
copy.Dispose();
}
This should match a raw oracle DDL performance:
create table table_name as select * from other_table_name