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.
Related
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 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.
I am writing a small program using an SQL database. The table name is StudentInfo.
I need to know the SQL code for the following
for (n=0; n<nRows; n++) {
string sql1="update StudentInfo set Position=" + n + " where <this has to be the row number>";
}
nRows is number of rows.
How can I get the row number for the above code?
best way to do this is to create a stored procedure in the database and use your code to pass the relevent information to the server
In order to accomplish this task you'll want to create a Stored Procedure or build a Query that actually accepts parameters. This will help you pass variables between, your method of concatenation will actually cause an error or become susceptible to SQL Injection attacks.
Non Parameter SQL Command:
using(SqlConnection sqlConnection = new SqlConnection("Database Connection String Here"))
{
string command =
"UPDATE Production.Product " +
"SET ListPrice = ListPrice * 2 " +
"WHERE ProductID IN " +
"(SELECT ProductID " +
"FROM Purchasing.ProductVendor" +
"WHERE BusinessEntityID = 1540);" +
using (SqlCommand sqlCommand = new SqlCommand(command, sqlConnection))
{
int execute = command.ExecuteNonQuery();
if( execute <= 0)
{
return false;
}
else
{
return true;
}
}
}
That method is essentially creation a connection, running our SQL Command, then we are using an integer to verify that it did indeed run our command successful. As you can see we simply using SQL to run our command.
The other important thing to note, you can't create a sub-query with an update; you have to create an update then run a select as the sub-query to hone in more specific data across so you can span across tables and so on.
The other alternative would be to use a parameter based query, where your passing variables between SQL and your Application.
I won't post code to that, because I believe you wrote the C# loop to demonstrate what you would like SQL to do for you. Which is only update particular rows; based on a specific criteria.
If you could post additional information I'd be more then happy to help you. But I'm just going to post what I believe you are trying to accomplish. Correct me if I'm wrong.
I am currently working on a solution in C# to copy database tables across from Oracle to PostgreSQL. Everything is working great apart from one thing. When I get to copying one of my table which contains sql statements in one of its fields it falls over due to having two single quotes back to back. The SQL statements MUST remain in the table as it is being used to make another program database agnostic.
Is there a way to write the following SQL but without having the two single quotes back to back as seen near the 'TRUE' value near the end of the line. I have also included my code below to show how the statement is built up in C#. The column is a varchar2 in Oracle and a TEXT column in PostgreSQL.
EDIT: The sql example shown is the actual INSERT statement generated by my C# code which will then be run on the postgresql database to add a record to a table. It will be used to insert a text field, among others, that contains an sql statement in the form of a string.
INSERT INTO SQL_FACTORY_TEST (SQL_FACTORY_TEST_ID,SQL_FACTORY_DIALECT,SQL_FACTORY_QUERY_NAME,SQL_FACTORY_SQL_COMMAND,USER_NAME)
VALUES (21, 'ORACLE', 'GET_CLUSTERS', 'SELECT CLUSTER_ID, NUM_POINTS, FEATURE_PK, A.CELL_CENTROID.SDO_POINT.X, A.CELL_CENTROID.SDO_POINT.Y, A.CLUSTER_CENTROID.SDO_POINT.X, A.CLUSTER_CENTROID.SDO_POINT.Y, TO_CHAR (A.CLUSTER_EXTENT.GET_WKT ()), TO_CHAR (A.CELL_GEOM.GET_WKT ()), A.CLUSTER_EXTENT.SDO_SRID FROM (SELECT CLUSTER_ID, NUM_POINTS, FEATURE_PK, SDO_CS.transform (CLUSTER_CENTROID, 4326) cluster_centroid, CLUSTER_EXTENT, SDO_CS.transform (CELL_CENTROID, 4326) cell_centroid, CELL_GEOM FROM :0) a where sdo_filter( A.CELL_GEOM, SDO_CS.transform(mdsys.sdo_geometry(2003, :1, NULL, mdsys.sdo_elem_info_array(1,1003,3),mdsys.sdo_ordinate_array(:2, :3, :4, :5)),81989)) = 'TRUE'', 'PUBLIC')
Code sample:
oleDataBaseConnection.OleExecutePureSqlQuery("SELECT * FROM " + tableName);
if (oleDataBaseConnection.HasRows())
{
while (oleDataBaseConnection.NextRecord())
{
Dictionary<string, string> postgreSQLQueries = TypeConversion.GetQueryDictionary("POSTGRESQL");
string postgreSQLInsertQuery;
postgreSQLQueries.TryGetValue("INSERT", out postgreSQLInsertQuery);
postgreSQLInsertQuery = postgreSQLInsertQuery.Replace("{0}", tableName);
StringBuilder postgresQuery = new StringBuilder();
postgresQuery.Append(postgreSQLInsertQuery);
postgresQuery.Append("(");
int columnCounter = 0;
//add a column parameter to query for each of our columns
foreach (KeyValuePair<string, string> t in columnData)
{
postgresQuery.Append(t.Key + ",");
columnCounter++;
}
postgresQuery = postgresQuery.Remove(postgresQuery.Length - 1, 1);
postgresQuery.Append(") ");
postgresQuery.Append("VALUES (");
//Loop through values and
for (int i = 0; i < columnCounter; i++)
{
string[] foo = new string[columnData.Count];
columnData.Values.CopyTo(foo, 0);
if (foo[i].ToUpper() == "TEXT")
{
postgresQuery.Append("'" + oleDataBaseConnection.GetFieldById(i) + "', ");
}
else
{
postgresQuery.Append(oleDataBaseConnection.GetFieldById(i) + ", ");
}
}
postgresQuery = postgresQuery.Remove(postgresQuery.Length - 2, 2);
postgresQuery.Append(") ");
postgresSQLDBConnection.PostgreSQLExecutePureSqlNonQuery(postgresQuery.ToString());
}
}
The best solution is to use a PreparedStatement where you don't pass literals directly. I don't know C#, so I can't give you an example for that.
However if you have to keep the statement like that, you can use a "dollar quoted" string literal in PostgreSQL.
INSERT INTO SQL_FACTORY_TEST (SQL_FACTORY_TEST_ID,SQL_FACTORY_DIALECT,SQL_FACTORY_QUERY_NAME,SQL_FACTORY_SQL_COMMAND,USER_NAME)
VALUES (21, 'ORACLE', 'GET_CLUSTERS', $$ ...... 'TRUE'$$, 'PUBLIC')
The $$ replaces the single quote(s) around the literal. If there is a chance that your value contains $$ you can also add some unique identifier to it, e.g.
$42$String with embedded single quotes ''' and two $$ dollar characters$42$
I think the best approach is to use a parameterised insert query. so you are actually building a query like:
INSERT INTO [Table] VALUES (#Column1, #Column2, #Column3)
then passing the variable into the query. Something along the lines of:
List<OleDbParameter> parameters = new List<OleDbParameter>();
for (int i = 0; i < columnCounter; i++)
{
postgresQuery.Append(string.Format("#Column{0}, ", i));
parameters.Add(new OleDbParameter(string.Format("#Column{0}, ", i), oleDataBaseConnection.GetFieldById(i));
}
Then pass paremeters.ToArray() to you OleDbCommand when executing. This means c# will do all the escaping, and work out the data type for you. It may need a bit of tweaking to suit your needs but the general gist of it is there.
You should always properly escape and unescape values stored in the database.
The PostgreSQL client library provides the PQescapeLiteral function for this purpose.
How to make this code properly? I am not satisfied with this code, I'm lost.
I give you a simple example but the query is more complexe.
Thanks in advance.
string aValue;
string queryA;
string queryB;
string finalQuery;
string queryA = #"SELECT column1 FROM table1 WHERE column1=";
queryA += aValue;
string queryB = #"SELECT column1, column2,"
if (aValue == "all"){
queryB += #"column3";
}
queryB += #"FROM table1 WHERE column1=";
queryB += #"'" +aValue+ "'";
private void exportExcel(){
// change the value with a dropdownlist
if (ddlType.selectedIndex(1))
aValue = "typeA";
else if(ddlType.selectedIndex(2))
aValue = "typeB";
else
aValue = "all";
// select the query
if (aValue == "typeA")
finalQuery = queryA;
else if (aValue == "typeB")
finalQuery = queryB;
ExecQUery(finalQuery);
}
In both Java and C# (and pretty much any other platform) you should definitely not include the values directly in the SQL. That's opening up the way to SQL injection attacks, and also makes dealing with formatting for dates, times and numbers tricky.
Instead, you should use parameterized SQL, specifying the values in the parameters. How you do that varies between Java and C#, but the principle is the same.
Another approach on both platforms is to use an ORM of some description rather than building queries by hand. For example, in .NET you might want to use a LINQ provider of some description, and in Java you might want to use something like Hibernate. Either way you get to express your queries at a higher level of abstraction than just the raw SQL.
It's hard to give much more concrete advice without knowing what platform you're really using (or database) and without a real query to look at.
One small change you can do is set value attribute of dropdownlist to typeA,TypeB, etc.. and get rid of the initial if conditions and variables.
eg:
if(ddlType.selectedValue.toString()=="typeA")
finalQuery = queryA;
if(ddlType.selectedValue.toString()=="typeB")
finalQuery = queryB;
I usually load it from a resource file. This gives you some freedom to change the queries (this in case you don't need to generate it dynamically with if blocks). In source code I use formatting ending my line with a comment line in order to avoid my IDE to concatenate or put it all in one like like:
String sql = "select " + //
" * " + //
"from "+ //
" employee " + //
"where " + //
" salary > :minSal " + //
" and startDate > :minStartDate";
And in case of conditional part I just add it with a if block. But for where statements I just add one default "1=1" in order to proceed with additional limitations, so if there is no additional limitations the query will still be valid. Suppose both where statements in the SQL bellow were added conditionally:
String sql = "select " + //
" * " + //
"from "+ //
" employee " + //
"where 1 = 1 ";
Until here you have your base SQL, valid, that means if not condition is added it will still be valid.
Suppose you will add salary limitation just in case if it is informed:
if (salary != null) {
sql += "and salary > :minSalary";
parameters.put("minSalary", salary);
}
As you can see in the same condition I add a new expression to my SQL and a parameter to a map that will be used later in execute to set the parameters to the query, that avoids you to create a second if statement just to set this parameter.
Another approach that you could take is build the entire SQL and before the execution ask for the prepared statement which parameters it needs as input and provide them. In java you can do it with:
http://download.oracle.com/javase/1.4.2/docs/api/java/sql/PreparedStatement.html#getParameterMetaData%28%29
I know that is not the case but if ORM is used, is common to have Builders for queries and this turns this task much easier, for example in Hibernate you could have something like:
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "F%")
.addOrder( Order.asc("name") )
.addOrder( Order.desc("age") )
.setMaxResults(50)
.list();
As it is documented at:
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/querycriteria.html
That means you could do this:
Criteria c = sess.createCriteria(Cat.class)
.addOrder( Order.asc("name") )
.addOrder( Order.desc("age") )
.setMaxResults(50);
if (name != null) {
c.add( Restrictions.like("name", name);
}
List cats = c.list();