I need to improve dramatically the execution time of a process wich currently is just like this:
private Dictionary<int, SingleElement> elements_buffer;
// ... Create and load values into "elements_buffer"
// (string, Datetime, double)
string query = "INSERT INTO Tests.dbo.test_table "
+ "(element_name,element_init,element_width) VALUES";
SingleElement element_aux;
for (int i = 0; i < MAX_ELEMS_IN_DICT; i++)
{
element_aux = elements_buffer[i];
query = query
+ "('"
+ element_aux.name
+ "','"
+ element_aux.init
+ "',"
+ element_aux.width
+ ")";
if (i < MAX_ELEMS_IN_DICT+1) {
query = query + ",";
}
}
// ... Execute the query
I was going to use Datatable for the new version, but I've been reading about using SqlBulkCopy together with IDatareader, as mentioned in:
SqlBulkCopy performance
C# loops and mass insert
It looks like to me a better choice for my code, but can't get to figure it out how to code it, though I would like to use it.
Can I have some help with the translated code, please?
Thanks in advance
As you already have figured out, you need to implement custom IDataReader.
As your source is Dictionary - only few relatively simple method/properties such as int FieldCount, bool Read(), object Get(int i) should be implemented.
You may find useful examples (a simple one) of custom IDataReader implementations by googling sqlbulkcopy custom idatareader.
Also keep in mind that SqlBulkCopy ignores triggers, foreign keys and other constraints and is not able to handle exceptions.
Related
This question is an extension to another I asked Here
I have a win form which has checkbox controls in it. The names of the checkboxes matches column names of a table. I can not normalize the tables cause of huge data involved, already received for the live project. so everything stays as it is.
I get the selected checbox names as a csv col1,col2,col3 which later i concatenate it to sql string.(no SPs as its a sql compact 3.5 sdf dbase).
In my GetData() method of the DataAccess class i form the sql string. But to avoid sql injections how can ensure that the column names passed are validated.
// Get Data
// selectedMPs: string csv, generated from the list of selected posts(checkboxes) from the UI, forming the col names in select
public static DataTable GetDataPostsCars(string selectedMPs, DateTime fromDateTime, DateTime toDateTime)
{
DataTable dt;
//string[] cols = selectedMPs.Split(','); //converts to array
//object[] cols2 = cols;//gets as object array
//=== using cols or cols 2 in String.Format does not help
// this WORKS, but as i am aware its prone to injections. so how can i validate the "selectedMPs" that those are columns from a list or dictionary or so on? i am not experienced with that.
string sql = string.Format(
"SELECT " + selectedMPs + " " +
"FROM GdRateFixedPosts " +
"WHERE MonitorDateTime BETWEEN '" + fromDateTime + "' AND '" + toDateTime +
using (cmd = new SqlCeCommand(sql,conn))
{
cmd.CommandType = CommandType.Text; //cmd.Parameters.Add("#toDateTime",DbType.DateTime);
dt = ExecuteSelectCommand(cmd);
}
return dt;
}
this WORKS, but as i am aware its prone to injections. so how can i validate the "selectedMPs" that those are columns from a list or dictionary or so on? i am not experienced with that. I would really appreciate your help. Thanks in advance.
This is the only possible approach, and there is no risk of injection with SQL Server Compact, as that database engine only executes a single statement per batch.
It's possible to perform this operation without "foreach" ???
I would understand if there are library functions to associate a parameter a list of data and perform N insert all together
String SQL_DETAIL = "INSERT INTO [ImportDetail] " +
" ([idMaster] " +
" ,[operation] " +
" ,[data]) " +
" VALUES " +
" (#a,#b,#c) ";
foreach (ImportDetail imp in this.lstImportDetail )
{
SqlCommand dettCommand = new SqlCommand(SQL_DETAIL, myTrans.Connection);
dettCommand.Transaction = myTrans;
dettCommand.Parameters.Add("a", SqlDbType.Int).Value = imp.IdMaster;
dettCommand.Parameters.Add("b", SqlDbType.NVarChar).Value = imp.Operation;
dettCommand.Parameters.Add("c", SqlDbType.NVarChar).Value = imp.Data;
i =i+ (int)dettCommand.ExecuteNonQuery();
}
Thank for help
If you were feeling frisky, you could create a user-defined table type and put the data from all your ImportDetail objects into a DataTable. This wouldn't totally alleviate the need for a loop, though -- it just makes it more efficient since you're only transforming data in memory instead of passing it to a remote database. Once you have your DataTable populated to match the schema of your table type, you can pass it as a parameter to a stored procedure to handle your INSERTs. The procedure can be made transactional if you'd like, and since your input data will already be in a table (of sorts), the SQL for the stored procedure should be pretty simple, as well.
For more info on user-defined table types and how they're used in .NET, check out this link: http://msdn.microsoft.com/en-us/library/bb675163.aspx
Going a little further, if this is something you have to do quite often, it's not out of the realm of possibility to turn this into some sort of extension method on IEnumerable to keep things DRY.
Is this the sort of solution you were looking for? It's a little hard for me to tell given the wording of the question.
You can define two array list:
the first will have the name of the parameter and the second its value:
ArrayList arrayName = new ArrayList { };
ArrayList arrayValue = new ArrayList { };
arrayName.Add("#Parameter1");
arrayName.Add("#Parameter2");
arrayValue.Add(value1);
arrayValue.Add(value2);
for (int i = 0; i < arrayName.Count; i++)
{
cmd.Parameters.AddWithValue(arrayName[i].ToString(), arrayValue[i]);
}
Then execute command
i have a list that i am pulling things out of to insert into a database. This is not going to be a web app so i have just been doing as follows:
string sqlStorage = (null,"asd"),
for (int i = 1; i < listsize; )
{
sqlStorage = sqlStorage + "(null,someVariableFromLoop)";
i++
}
string connString = "Server=localhost;...........";
MySqlConnection conn = new MySqlConnection(connString);
MySqlCommand command = conn.CreateCommand();
command.CommandText = #"INSERT INTO table1 VALUES " + tempSQLStorage;
etcetc...
However
"someVariableFromLoop"
is a large amount of text which includes all kinds of horrible code breaking characters. quotation marks etc etc.
So i looked into parameters (the way i should be doing SQL i know, i know), however i was unable to find a way to store these parameters inside the loop. i dont want to hit the DB every single iteration. I had a go at something along the lines of
"#variable"+i.toString();
but could not get it to work at all.
So does anyone have any idea how i would go about storing the parameters and the execute the query? Thanks in advance!
So i looked into parameters (the way i should be doing SQL i know, i know), however i was unable to find a way to store these parameters inside the loop. i dont want to hit the DB every single iteration. I had a go at something along the lines of
"#variable"+i.toString();
but could not get it to work at all.
Well, what was the error you received? Because that's the way you do it. Here's an example for MSSQL and I know the technique works, because I've done similar before:
int i = 0;
List<string> clauses = new List<string>() {"(#key0, #value0)"};
List<SqlParameter> paramList = new List<SqlParameter> {
new SqlParameter("#key0", DBNull.Value),
new SqlParameter("#value0", "asd")
};
for (i = 1; i < listSize; i++) {
clauses.Add("(#key" + i + ", #value" + i + ")");
paramList.Add(new SqlParameter("#key" + i, someKey));
paramList.Add(new SqlParameter("#value" + i, someValue);
}
SqlConnection conn = new SqlConnection(connString);
SqlCommand command = new SqlCommand(conn, #"INSERT INTO table1 VALUES " + String.Join(", ", clauses);
foreach(SqlParameter param in paramList) command.Parameters.Add(param);
command.ExecuteNonQuery();
Note, above code is quick and dirty. Obviously using statements and various other best practices should be incorporated as well for production code.
Also look at this: How do you use the MySql IN clause. It has an example of dynamically creating and passing parameters to the query, but for an SELECT...IN clause vs. INSERT...VALUES.
To ensure secure code (and avoid malformed queries), use SQL Command objects with Parameters. There is nothing horribly wrong with executing the command once for every record - a little extra overhead for round-trips over the network, but if the text is long you might have to do this anyway since queries do have a character limit.
I currently have this function (see below) which adds answers and related content (e.g. notes) to 50 questions or so. I'm trying to make the database secure by changing it to a stored procedure so I can remove the write permission from the users. Is this the right approach?
Should I use table-valued parameters? Or should I call the stored procedure 50 times? The former feels like it's right, but the latter would look nicer in code although I expect is inefficient?
Or ... is there a better way/approach?
int AddAnswersToTable()
{
var Cmd = Db.NewCommand();
var Str = new StringBuilder("INSERT INTO answers (qu_id,answer,notes) VALUES");
int i = 1;
string Join = string.Empty;
foreach (var Qu in Page)
{
Str.Append(Join);
Str.Append("(");
Str.Append((Name = "#qu_id" + i) + ",");
Cmd.Parameters.AddWithValue(Name, Qu.QuId);
Str.Append((Name = "#ans" + i) + ",");
Cmd.Parameters.AddWithValue(Name, Qu.Answer.Truncate(499));
Str.Append((Name = "#notes" + i) + ",");
Cmd.Parameters.AddWithValue(Name, Qu.Notes.Truncate(499));
Str.Append(")");
Join = ",";
i++;
}
return Cmd.ExecuteNonQuery(Str.ToString());
}
Thanks in advance.
I haven't used Table Valued Parameters so I can't say if that is the way to go. But another option would be to do batch updates using SqlDataAdapter.
By setting the "UpdateBatchSize" property you can send multiple values/rows at a time.
You can read about it at MSDN: http://msdn.microsoft.com/en-us/library/aadf8fk2%28v=vs.100%29.aspx
And here is an article where a stored procedure is used: http://www.dotnetspider.com/resources/4467-Multiple-Inserts-Single-Round-trip-using-ADO-NE.aspx
Whether a stored procedure is the right approach is a religious matter. I believe it is, FWIW.
Calling the procedure 50 times is simpler, but neither approach is wrong, per se.
All right, this is the bigger question linked to this link which I tried to delete but couldnt any more. People said I should never post part of the problem due to the x y problem link, which is fair enough. So here it comes.
Lets say I have a class:
public class CustomClass
{
public string Year;
public double val;
public string Tariff;
public string ReportingGroup;
}
I now have some process that creates a list of this class with results (in reality its a bigger class but that shouldnt matter).
I now create an Access table if it doesnt exist yet. For this I need the class members and ideally also the type (currently all text!):
public static void createtable(string path, string tablename, string[] columnnames)
{
try
{
string connectionstring = creadteconnectionstring(path);
OleDbConnection myConnection = new OleDbConnection(connectionstring);
myConnection.Open();
OleDbCommand myCommand = new OleDbCommand();
myCommand.Connection = myConnection;
string columnam = "[" + columnnames[0] + "] Text";
for (int i = 1; i < columnnames.Length; i++)
{
columnam = columnam + ", [" + columnnames[i] + "] Text";
}
myCommand.CommandText = "CREATE TABLE [" + tablename + "](" + columnam + ")";
myCommand.ExecuteNonQuery();
myCommand.Connection.Close();
Console.WriteLine("Access table " + tablename + " created.");
}
catch
{
Console.WriteLine("Access table " + tablename + " already exists.");
return;
}
}
Note column name contains actually the names of the class members of custom class. Then I paste the data into it:
public static void appenddatatotable(string connectionstring, string tablename, string datstr, List<CustomClass> values)
{
string commandtext = "INSERT INTO " + tablename + " ([RunDate],[ReportingGroup], [Tariff], [Year], [Quarter]) VALUES(#RunDate, #ReportingGroup, #Tariff, #Year, #Quarter)";
using (var myconn = new OleDbConnection(connectionstring))
{
myconn.Open();
using (var cmd = new OleDbCommand())
{
foreach (var item in values)
{
cmd.CommandText = commandtext;
if (string.IsNullOrEmpty(item.val))
item.val = "";
cmd.Parameters.Clear();
cmd.Parameters.AddRange(new[] { new OleDbParameter("#RunDate", datstr), new OleDbParameter("#ReportingGroup", item.RG), new OleDbParameter("#Tariff", item.tar), new OleDbParameter("#Year", item.yr), new OleDbParameter("#Quarter", item.val)});
cmd.Connection = myconn;
//cmd.Prepare();
cmd.ExecuteNonQuery();
}
}
}
}
This all works fine.
However, say I change sth in my process that also needs another calculation that yields value2, then I need to change the class, the createtable and teh appenddatatotable function. I would like to only update the class.
So, you are trying to build your own ORM (Object Relational Mapper) for C# and MS Access databases.
While this is an interesting endeavour as a learning experience, it's a problem that is hard to tackle properly.
What you need to do is use reflection in your createtable to determine the details metadata necessary (property names, property types) to construct the CREATE TABLE SQL Statement.
Then you could use something like DBUtils.CreateTable<CustomClass>(connStr); to create the table.
Since you have not mentioned reflection in this question, you really need to first learn as much as you can about it, and experiment with it first before you can answer your own question.
You previous question had some answers that already mentioned using reflection and showed you how to get the property names and types of arbitrary classes.
Once you get through that hurdle, you will encounter other problems:
How to define type lengths
Especially for strings, in .Net they can be considered almost unlimited (for most use anyway) but in Access, a string of less than 255 characters is not the same type as a larger one.
How to define your Primary key.
As a general rule, all tables in a database must have a Primary Key field that is used to identify each record in a table in a unique way.
In an ORM, it's really important, so you can easily fetch data based on that key, like GetByID<CustomClass>(123) would return an instance of your CustomClass that contains the data from record whose primary key ID is 123.
How to define indexes in your database.
Creating tables is all good and well, but you must be able to define indexes so that queries will have expected performance.
How to define relationships between tables.
Databases are all about relational data, so you need a way to define these relationships within your classes so that a class PurchaseOrder can have a list of PurchaseItem and your code understand that relationship, for instance when you need to delete a given Purchase Order, you will also need to delete all of its items in the database.
How to only load what you need.
Say you have a Customer class that has a PurchaseOrders property that is in fact a List<PurchaseOrders>. Now, if you load the data of a particular customer, to display their phone number for instance, you do not want to also pull all the possible 1,000s or orders they have made over the years, each of these having maybe 100s of items...
How to execute queries and use their results.
Once you have mapped all your tables to classes, how do you query your data?
linq is fantastic, but it's very hard to implement by yourself, so you need a good solution to allow you to make queries and allow your queries to return typed data.
For many of these issues, custom Attributes are the way to go, but as you move along and make your ORM more powerful and flexible, it will increase in complexity, and your early decisions will sometimes weigh you down and complicate things further because, let's face it, building an ORM from scratch, while an interesting experience, is hard.
So, you really have to think about all these questions and set yourself some limits on what you need/want from the system before jumping into the rabbit hole.