Im using Visual Studio to create a program. In this program i have to look up for all the tables created and see if a column from a list exists, if not create it. To do that i created this variables:
Dictionary<string, List<Dictionary<string, string>>> TABLE_DICT //the string saves the name of the table, the list the columns and types
List<Dictionary<string, string>> TABLE_LIST = new List<Dictionary<string, string>>(); //list of columns in a table with its type
Dictionary<string, string> DICT = new Dictionary<string, string>(); // name of column and the type of this column
The code is the following:
try
{
conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + DBstring);
conn.Open();
foreach (KeyValuePair<string, List<Dictionary<string, string>>> TABLE_kvp in TABLE_DICT)
{
foreach (Dictionary<string, string> dic in TABLE_kvp.Value)
{
foreach (KeyValuePair<string, string> kvp in dic)
{
DataTable dt = conn.GetSchema("Columns", new string[] { null, null, TABLE_kvp.Key, kvp.Key });
if (dt.Rows.Count == 0)
{
OleDbCommand command = new OleDbCommand();
command.Connection = conn;
command.CommandText = "ALTER TABLE ? ADD COLUMN ? ? ";
command.Parameters.AddWithValue("#tablename", TABLE_kvp.Key);
command.Parameters.AddWithValue("#col", kvp.Key);
command.Parameters.AddWithValue("#val", kvp.Value);
command.ExecuteNonQuery();
command.Connection.Close();
}
}
}
}
}
catch (OleDbException ex)
{
ErrorForm ef = new ErrorForm(ex.Message, this.BackColor);
ef.ShowDialog(this);
}
The problem is that the code catch an exception for syntax error, even when i chanche the command text for the following stll does a syntax error:
command.CommandText = "ALTER TABLE ? ADD COLUMN [?] ? NULL";
command.CommandText = "ALTER TABLE ? ADD COLUMN [?] ?";
command.CommandText = "ALTER TABLE [?] ADD COLUMN ? ? ";
command.CommandText = "ALTER TABLE ? ADD COLUMN ? MEMO ";
Searching in google, at least the last one of the list, do not have to make an error,or this is what i believe, but still does. Any idea?
DDL statements can't be parametrized. You need to concatenate it in code. And because you need to do it this way, you must either
Validate the parameters, or
Be absolutely sure your parameters are trusted.
Example code with validation:
var validator = new Regex(#"^\w+$");
if (new[] { TABLE_kvp.Key, kvp.Key, kvp.Value }.All(validator.IsMatch))
{
command.CommandText = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2}",
TABLE_kvp.Key,
kvp.Key,
kvp.Value);
}
Bind variables/parameters are for values only, not for table names. You have to change the table names as part of your application logic.
On top of that you cannot run DDL commands using parameters. It is only meant for DML (INSERT or UPDATE or DELETE or invoking stored procedures)
Related
Having SQL with parameter (passing through SelectCommand.Parameters.AddWithValue),
can I get easily final SQL to run it immediately? I mean, not to have unresolved parameters in the command, but rather fully resolved SQL. I can do that manually by Replace function, but I have to deal with types, etc. So just wondering if there is a way to grab the SQL statement processed by SQL engine directly (something what I can see in SQL Profiler). E.g. to have
SELECT Name FROM TABLE WHERE Id = 15
instead of
SELECT Name FROM TABLE WHERE Id = #Id
I need to log whole SQL that is runnable copy&paste&run. I can log command and parameters, but in this approach I have to manually construct the SQL statement.
DataTable dt = new DataTable();
query = "SELECT Name FROM TABLE WHERE Id = #Id";
using (SqlDataAdapter da = new SqlDataAdapter(query, String.Format(#"Data Source={0};Initial Catalog={1};Integrated Security=SSPI", relServer, relDatabase)))
{
if (QueryParams != null && QueryParams.Count > 0)
{
foreach (KeyValuePair<string, object> entry in QueryParams)
{
da.SelectCommand.Parameters.AddWithValue(entry.Key, entry.Value ?? DBNull.Value);
}
}
da.Fill(dt);
}
I need to save a sql SELECT Statement, which includes all tables and its columns in a database. The statement works fine and i can get all the names from the tables and columns i need.
The result looks as follows: (this is just psuedo-something)
table_Name Column_name
- CallerIP DT
- CallerIP ID
- CallerIP IP
- queueObject Action
- queueObject Attempt
- queueObject DestinationAddress
- queueObject ID
I thougt, i can save it into a Dictionary, where the tableName is a String, and the Colum_Names is a List of Strings
private Dictionary<string, List<string>> rowAndTables = new Dictionary<string, List<string>>();
this is my code, which should add all the tables and rows into the Dictionary
//Some code above, that doesnt matter here
command = new SqlCommand(sqlSelect, SqlConnector.getConnection());
command.Connection = SqlConnector.getConnection();
reader = command.ExecuteReader();
while (reader.Read()) {
if (tempTableName.Equals(reader.GetString(0)) == false) {
tempTableName = reader.GetString(0);
tempColumn = reader.GetString(1);
Console.WriteLine(tempTableName);
Console.WriteLine(tempColumn);
} else {
tempColumn = reader.GetString(1);
Console.WriteLine(tempColumn);
}
}
This doesnt do anything, besides printing all tables and columns.
The result looks as follows:
//CONSOLE...
CallerIP //Table
DT
ID
IP
queue_object //Table
Action
Attempt
DestinationAddress
ID
So the printing is fine.
Now I am struggeling with adding it into a Dictionary.
Can anyone help ?
Anything I did made no sense, and would just confuse anyone, I guess.
Well, if you want to fill the dictionary
private Dictionary<string, List<string>> rowAndTables =
new Dictionary<string, List<string>>();
you should modify your code slightly:
...
//DONE: wrap IDisposable (command) into using in order to release resources
using (var command = new SqlCommand(sqlSelect, SqlConnector.getConnection())) {
// Redundant, can be dropped
command.Connection = SqlConnector.getConnection();
using (var reader = command.ExecuteReader()) {
//TODO: check if the key and value are correct ones
string key = Convert.ToString(reader[0]);
string value = Convert.ToString(reader[1]);
// Do we have the key (and corresponding list) in the dictionary?
if (rowAndTables.TryGetValue(key, out var list))
// yes - we should add the value to the existing list
list.Add(value);
else
// no - we have to create key and list with value
rowAndTables.Add(key, new List<string>() {value});
}
}
I have to pass data in some structured format which is at the code side in a List of an anonymous type to the SQL which is then converted into a Temp table and therefore used in further joins. Currently, I am looping through the data and creating a string using StringBuilder which is actually CREATE temp table and INSERT INTO statements.
Below is my code
StringBuilder sql = new StringBuilder();
sql.Append(#"
IF OBJECT_ID('tempdb..#Student') IS NOT NULL
DROP TABLE #Student
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Student (StudentId int,StudentName varchar(500),Marks int, DepartmentId int);");
foreach (var item in studentData)
{
sql.AppendLine();
sql.AppendLine("INSERT INTO #Student (");
StringBuilder values = new StringBuilder("VALUES (");
bool isFirstColumn = true;
Type type = item.GetType();
PropertyInfo[] propertyInfo = type.GetProperties();
foreach (PropertyInfo property in propertyInfo)
{
string columnName = property.Name;
object columnValue = property.GetValue(item);
if (isFirstColumn)
isFirstColumn = false;
else
{
sql.Append(", ");
values.Append(", ");
}
sql.Append(columnName);
values.Append(columnValue);
}
sql.Append(") ");
sql.AppendLine();
sql.Append(values.ToString());
sql.Append(")");
}
sql.AppendLine();
sql.Append(#"SELECT *
INTO #Temp
FROM
(
SELECT *
FROM #Student s
JOIN Department d ON s.DepartmentId = d.DepartmentId
) as t");
I dont have the option of using Stored Proc. Is there any better option to create Temp table than using StringBuilder which will be then inserted in SQL query?
Can we convert the data in the List into XML and then add the data in XML format in the string SQL query?
Any help or suggestions for a better code than this?
Thanks
Since you are using ADO.NET, you can use Table-Valued Parameters and pass the Table-Valued Type to a parameterized SQL statement. I'd opt for using a stored procedure, but both will work.
modified example usage from MS Docs (not tested)
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedStudents = StudentsDataTable.GetChanges(DataRowState.Added);
// Define the INSERT-SELECT statement.
string sqlInsert =
"INSERT INTO dbo.Students (xxx, yyy)"
+ " SELECT s.xxx, s.yyy"
+ " FROM #tvpStudents AS s;"
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#tvpStudents", addedStudents);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.StudentTableType";
// Execute the command.
insertCommand.ExecuteNonQuery();
}
I have this user table type in SQL Server:
CREATE TYPE [dbo].[ListNew] AS TABLE
(
[Id] [int] NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC) WITH (IGNORE_DUP_KEY = OFF)
)
GO
And use this type in stored procedure parameter:
....
(#lstNew ListNew READONLY,
#UserName nvarchar(128))
AS
....
And using this stored procedure in ASP.NET MVC with this code:
List<int> lstNew = MyList.Select(o => o.Key).ToList();
List<XXXView> lstView = db.Database.SqlQuery<XXXView>("MyStoredProcedure #lstNew,#UserName",
new SqlParameter("#lstNew", lstNew),
new SqlParameter("#UserName", userName)).ToList();
but it's not working and get this error:
No mapping exists from object type System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] to a known managed provider native type.
I try without ListNew and used only username, it's working
Edit:
I use this code:
myParameter.SqlDbType = SqlDbType.Udt;
myParameter.UdtTypeName = "ListNew";
But I get the same warning
This is a solved problem and properly documented in - cough - the documentation.
YOu will need to define the table on the server side and then can pass in a table valued parameter.
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters
This runs down to using the SqlDbType Structured.
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.CategoryTableType";
You CAN use a DataTable, but then you introduce the most overhead approach possible as object model - or you just use...
https://forums.asp.net/t/1845039.aspx?Table+Value+Parameter+Use+With+C+without+using+DataTable
Basically you transform your data into SqlDataRecords and pass them in. Needs some metadata - but generally this can be generalized and fits in below a page of code. The link has the code (which I can not copy here due to - well - it not being MY code).
I always use XML to pass these type of DATA to sqlserver sproc.
When you pass your XML to sproc you can use something like this to have it as a table in your sproc:
(commented lines are my XML's structure. manipulate it for your own use.)
EXEC sp_xml_preparedocument #XmlHandle output,#FieldPermissionAccessXML
--'<FieldPermissions>
--<FieldPermission FieldName="" RoleID="1" Access="1" />
--<FieldPermission FieldName="" RoleID="2" Access="1" />'
--select * from JMP_FieldPermissions
INSERT INTO #FieldPermissionsTable
SELECT *--ID,Value, Value2, Navi_User
FROM OPENXML (#XmlHandle, './Row_ID/Elements')
WITH (TE_ID VARCHAR(MAX) '#ID',
Value VARCHAR(MAX) '#Value',
Value2 VARCHAR(MAX) '#Value2',
NAVI_USER VARCHAR(MAX) '#Navi_User'
)
Create the DataTable variable in c#, put the data in the DataTable, pass it to sp:
List<int> lstNew = MyList.Select(o => o.Key).ToList();
DataTable lstNewTable = new ListNew();
foreach (var id in lstNew )
{
lstNewTable.Rows.Add(id);
}
List<XXXView> lstView = db.Database.SqlQuery<XXXView>("MyStoredProcedure #lstNew,#UserName",new SqlParameter("#lstNew", lstNewTable),
new SqlParameter("#UserName", userName)).ToList();
Several answers were close but none gave a full working model. #Alison Niu just needs to add a column & name to populate the DataTable. #Saurabh Gadani the reflection is very flexible but the Props are Null. Also the cmd.Parameters needs some special values set for Sql to find the table definition. I am grateful for these answers that got me closer to a solution.
Populate a DataTable from a List of Integers
private DataTable ListInt_ToTable(List<int> tableThese)
{
DataTable lstNewTable = new DataTable();
//Columns
lstNewTable.Columns.Add("ID", typeof(int));
//Values
foreach (var id in tableThese)
{
lstNewTable.Rows.Add(id);
}
return lstNewTable;
}
The TYPE defines the table layout for SQL to receive the parameter. It can be written within the Stored Procedure but this is how I prefer to keep it in the application that built the data:
public static string SqlCmd_spStackOverflow_Loaded_ListIDs = #"
IF TYPE_ID(N'IdLoadedTableType') IS NULL
Begin
CREATE TYPE dbo.IdLoadedTableType
AS TABLE ( ID INT );
End
EXEC dbo.spStackOverflow_Loaded_ListIDs
#TblIds
";
Sending the Sql to run the Stored Procedure. Notice the parameters **
DataTable mappingTbl = ListInt_ToTable(mappingData);
using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))
{
try
{
DataTable results = new DataTable();
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(SqlCmd_spStackOverflow_Loaded_ListIDs, sqlConnection);
//apply parameters
//NOT VIA sqlDataAdapter.SelectCommand.Parameters.AddWithValue(...);
var specialParm = new SqlParameter(); // **
specialParm.ParameterName = "#TblIds"; // acts like a Declare in SQL
specialParm.Value = (object)mappingTbl; // sets the data valaes
specialParm.SqlDbType = SqlDbType.Structured; // unique to User-Defined Table parameters
specialParm.TypeName = "dbo.IdLoadedTableType"; // refers to created type
sqlDataAdapter.SelectCommand.Parameters.Add(specialParm); // done
sqlDataAdapter.Fill(results);
return results;
}
catch (Exception ex)
{
throw new Exception("An error occurred while executing a SQL Read statement with Parameters. SQL Statement: " + SqlCmd_spStackOverflow_Loaded_ListIDs + " Exception: " + ex.Message);
}
}
This model is working in my solution, except I use long in C# & BigInt in sql. Please improve options as you find something.
You can not pass any generic type list as SP parameter, must have to pass it as DataTable instead of List.
Here is an eaxample:
DataTable mappingTbl = ListToDataTable(mappingData);
SqlConnection con = new SqlConnection(conStr);
con.Open();
SqlCommand cmd = new SqlCommand(StoredProcedure.GetSavedFormList, con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#UDT_EBFromMappingTable", mappingTbl);
public DataTable ListToDataTable<T>(List<T> items)
{
DataTable dataTable = new DataTable(typeof(T).Name);
//Get all the properties
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
//Defining type of data column gives proper data table
var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType);
//Setting column names as Property names
dataTable.Columns.Add(prop.Name, type);
}
foreach (T item in items)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
//inserting property values to datatable rows
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
//put a breakpoint here and check datatable
return dataTable;
}
May this helps you. :)
I am receiving a Dictionary<string, string> and would like to forward its values to the DB inside SqlParameter. Is that even possible? This is the way I did it, and I am getting an error that column name doesn't match table definition.
SqlParameter param = new SqlParameter();
param.ParameterName = "#Values";
var sb = new StringBuilder();
foreach (var item in data)
{
sb.Append("'" + item.Value + "', ");
}
param.Value = sb.ToString().TrimEnd(',');
string insertString = $"insert into {tableName} values (#Values)";
SqlCommand command = new SqlCommand(insertString, connection);
command.Parameters.Add(param);
command.ExecuteNonQuery();
Sql server can't interpret the single variable you are passing as multiple values.
You can either generate your query with multiple variables, or use a table valued parameter.
For the first option, you must change the way you build your query:
var command = new SqlCommand();
var insertString = $"insert into {tableName} values (";
var sb = new StringBuilder(insertString);
int i = 0;
foreach (var item in data)
{
sb.Append("#P").Append(i).Append(",");
command.Parameters.Add("#P" + i, SqlDbType.VarChar).Value = item.Value;
i++;
}
command.Connection = connection;
command.CommandText = sb.ToString().TrimEnd(",") + ");";
command.ExecuteNonQuery();
Note: Code was not tested, there might be some errors.
For the second option, You must use a stored procedure. I've never tried to pass table valued parameters to an inline query and I don't think it's possible.
This post (also linked in Alex K's comment) explains how to do that.
Each value in the "Values" part of you t-SQL must be enclosed with parenthesis.
So, just change this line:
sb.Append("'" + item.Value + "', ");
to:
sb.Append("('" + item.Value + "'),"); // note: no space after the ,
Your tSQL would look something like this:
insert into myTable values ('A', 'B', 'C',)
It needs to look like this (assuming you've only got 1 column in the table):
insert into myTable values ('A'), ('B'), ('C')
And if your table contains multiple columns:
insert into myTable (myColumn) values ('A'), ('B'), ('C')
I think the best is create a split function in mssql (million of example in internet)and a stored. Pass a string comma(for example) separated to the stored Who call the function. Sorry for no example but i'm with my smartphone