I'm loading data into a form with 3 Entry controls.
The object I am using for this is called mySettings, which is an object of SystemSettings, a class and database table in my SQLite database.
So far I have this code, and it works as is.
var db = new SQLiteConnection(dbPath);
Entry txtServer;
txtServer = new Entry { FontSize = 10 };
controlGrid.Children.Add(txtServer, 2, 0);
Grid.SetColumnSpan(txtServer, 4);
SystemSettings mySettings;
mySettings = db.Get<SystemSettings>(0);
txtServer.Text = mySettings.FTPServer;
However, I need to check whether SystemSettings contains any rows in the table before I load values in.
I've seen a few guides online.
Some say use something along the lines of
SQLiteCommand cmd;
cmd = new SQLiteCommand(db);
...
int result = Convert.ToInt32(db.ExecuteScalar)
However, I get an error there saying
SQLiteCommand does not contain any method containing x parameters
no matter how many I pass in (0 or more).
There also doesn't appear to be a method as part of db.
So how can I check whether SystemSettings contains any rows, before trying to use data that doesn't exist?
The pattern below should work. The .ExecuteScalar() method is actually on the command and not the connection.
int count;
using (SQLiteConnection db = new SQLiteConnection("MY_CXN_STRING"))
using (SQLiteCommand cmd = new SQLiteCommand("SELECT COUNT(*) FROM SystemSettings"))
{
db.Open();
count = (int)cmd.ExecuteScalar();
db.Close();
}
bool hasRows = count != 0;
Basically you want to clear
SystemSettings
Try just running a query that returns nothing against the database. For instance:
SystemSettings = $"SELECT * FROM TABLE_NAME WHERE COLUMN_NAME IS 'INVALID_EXPRESSIONdjeiq48724rufnjdrandom stuff'";
Not the most elegant solution by any means, but it works.
What you want to do is to get the first row in you SystemSettings table if any:
You should therefore execute the following Sql Statement (or something similar) and check if a result is returned:
Select * from SystemSettings LIMIT 1;
You can execute the query and check the result like this:
public bool DoesTableContainRows(string tableName, SQLiteConnection connection)
{
var command = new SQLiteCommand($"Select * from {tableName } LIMIT 1;", connection);
var resultReader = command.ExecuteReader();
// check whether or not a row was returned
bool containRows = resultReader.Read();
resultReader.Close();
return containRows;
}
Edit:
Shows how to check if a table contains rows using .NET and Microsoft.Data.Sqlite including better disposing of resources.
public bool DoesTableContainRows(string tableName, SqliteConnection connection)
{
using (var command = new SqliteCommand($"Select * from {tableName } LIMIT 1;", connection))
{
using (var resultReader = command.ExecuteReader())
{
// check whether or not a row was returned
bool containRows = resultReader.Read();
resultReader.Close();
return containRows;
}
}
}
Related
I'm trying to port some old VB6 code to C# and .NET.
There are a number of places where the old code uses a RecordSet to execute a SQL query and then loop through the results. No problem so far, but inside the loop the code makes changes to the current row, updating columns and even deleting the current row altogether.
In .NET, I can easily use a SqlDataReader to loop through SQL query results, but updates are not supported.
So I've been playing with using a SqlDataAdapter to populate a DataSet, and then loop through the rows in a DataSet table. But the DataSet doesn't seem very smart compared to the VB6's old RecordSet. For one thing, I need to provide update queries for each type of edit I have. Another concern is that a DataSet seems to hold everything in memory at once, which might be a problem if there are many results.
What is the best way to duplicate this behavior in .NET? The code below shows what I have so far. Is this the best approach, or is there another option?
using (SqlConnection connection = new SqlConnection(connectionString))
{
DataSet dataset = new DataSet();
using (SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand(query, connection)))
{
adapter.Fill(dataset);
DataTable table = dataset.Tables[0];
foreach (DataRow row in table.Rows)
{
if ((int)row["Id"] == 4)
{
if ((int)row["Value1"] > 0)
row["Value2"] = 12345;
else
row["Value3"] = 12345;
}
else if ((int)row["Id"] == 5)
{
row.Delete();
}
}
// TODO:
adapter.UpdateCommand = new SqlCommand("?", connection);
adapter.DeleteCommand = new SqlCommand("?", connection);
adapter.Update(table);
}
}
Note: I'm new to the company and can't very well tell them they have to change their connection strings or must switch to Entity Framework, which would be my choice. I'm really looking for a code-only solution.
ADO.NET DataTable and DataAdapter provide the closest equivalent of ADO Recordset with applies separation of concens principle. DataTable contains the data and provides the change tracking information (similar to EF internal entity tracking) while DataAdapter provides a standard way to populate it from database (Fill method) and apply changes back to the database (Update method).
With that being said, what are you doing is the intended way to port the ADO Recordset to ADO.NET. The only thing you've missed is that you are not always required to specify Insert, Update and Delete commands. As soon as your query is querying a single table (which I think was a requirement to get updateable Recordset anyway), you can use another ADO.NET player called DbCommandBuilder:
Automatically generates single-table commands used to reconcile changes made to a DataSet with the associated database.
Every database provider provides implementation of this abstract class. The MSDN example for SqlCommandBuilder is almost identical to your sample, so all you need before calling Update is (a bit counterintuitive):
var builder = new SqlCommandBuilder(adapter);
and that's it.
Behind the scenes,
The DbCommandBuilder registers itself as a listener for RowUpdating events that are generated by the DbDataAdapter specified in this property.
and dynamically generates the commands if they are not specifically set in the data adapter by you.
I came up with an (untested) solution for a data table.
It does require you to do some work, but it should generate update and delete commands for each row you change or delete automatically, by hooking up to the RowChanged and RowDeleted events of the DataTable.
Each row will get it's own command, equivalent to ADODB.RecordSet update / delete methods.
However, unlike the ADODB.RecordSet methods, this class will not change the underling database, but only create the SqlCommands to do it. Of course, you can change it to simply execute them on once they are created, but as I said, I didn't test it so I'll leave that up to you if you want to do it. However, please note I'm not sure how the RowChanged event will behave for multiple changes to the same row. Worst case it will be fired for each change in the row.
The class constructor takes three arguments:
The instance of the DataTable class you are working with.
A Dictionary<string, SqlDbType> that provides mapping between column names and SqlDataTypes
An optional string to represent table name. If omitted, the TableName property of the DataTable will be used.
Once you have the mapping dictionary, all you have to do is instantiate the CommandGenerator class and iterate the rows in the data table just like in the question. From that point forward everything is automated.
Once you completed your iteration, all you have to do is get the sql commands from the Commands property, and run them.
public class CommandGenerator
{
private Dictionary<string, SqlDbType> _columnToDbType;
private string _tableName;
private List<SqlCommand> _commands;
public CommandGenerator(DataTable table, Dictionary<string, SqlDbType> columnToDbType, string tableName = null)
{
_commands = new List<SqlCommand>();
_columnToDbType = columnToDbType;
_tableName = (string.IsNullOrEmpty(tableName)) ? tableName : table.TableName;
table.RowDeleted += table_RowDeleted;
table.RowChanged += table_RowChanged;
}
public IEnumerable<SqlCommand> Commands { get { return _commands; } }
private void table_RowChanged(object sender, DataRowChangeEventArgs e)
{
_commands.Add(GenerateDelete(e.Row));
}
private void table_RowDeleted(object sender, DataRowChangeEventArgs e)
{
_commands.Add(GenerateDelete(e.Row));
}
private SqlCommand GenerateUpdate(DataRow row)
{
var table = row.Table;
var cmd = new SqlCommand();
var sb = new StringBuilder();
sb.Append("UPDATE ").Append(_tableName).Append(" SET ");
var valueColumns = table.Columns.OfType<DataColumn>().Where(c => !table.PrimaryKey.Contains(c));
AppendColumns(cmd, sb, valueColumns, row);
sb.Append(" WHERE ");
AppendColumns(cmd, sb, table.PrimaryKey, row);
cmd.CommandText = sb.ToString();
return cmd;
}
private SqlCommand GenerateDelete(DataRow row)
{
var table = row.Table;
var cmd = new SqlCommand();
var sb = new StringBuilder();
sb.Append("DELETE FROM ").Append(_tableName).Append(" WHERE ");
AppendColumns(cmd, sb, table.PrimaryKey, row);
cmd.CommandText = sb.ToString();
return cmd;
}
private void AppendColumns(SqlCommand cmd, StringBuilder sb, IEnumerable<DataColumn> columns, DataRow row)
{
foreach (var column in columns)
{
sb.Append(column.ColumnName).Append(" = #").AppendLine(column.ColumnName);
cmd.Parameters.Add("#" + column.ColumnName, _columnToDbType[column.ColumnName]).Value = row[column];
}
}
}
As I wrote, this is completely untested, but I think it should be enough to at least show the general idea.
Your constraints:
Not using Entity Framework
DataSet seems to hold everything in memory at once, which might be a
problem if there are many results.
a code-only solution ( no external libraries)
Plus
The maximum number of rows that a DataTable can store is 16,777,216
row MSDN
To get high performance
//the main class to update/delete sql batches without using DataSet/DataTable.
public class SqlBatchUpdate
{
string ConnectionString { get; set; }
public SqlBatchUpdate(string connstring)
{
ConnectionString = connstring;
}
public int RunSql(string sql)
{
using (SqlConnection con = new SqlConnection(ConnectionString))
using (SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.CommandType = CommandType.Text;
con.Open();
int rowsAffected = cmd.ExecuteNonQuery();
return rowsAffected;
}
}
}
//------------------------
// using the class to run a predefined patches
public class SqlBatchUpdateDemo
{
private string connstring = "myconnstring";
//run batches in sequence
public void RunBatchesInSequence()
{
var sqlBatchUpdate = new SqlBatchUpdate(connstring);
//batch1
var sql1 = #"update mytable set value2 =1234 where id =4 and Value1>0;";
var nrows = sqlBatchUpdate.RunSql(sql1);
Console.WriteLine("batch1: {0}", nrows);
//batch2
var sql2 = #"update mytable set value3 =1234 where id =4 and Value1 =0";
nrows = sqlBatchUpdate.RunSql(sql2);
Console.WriteLine("batch2: {0}", nrows);
//batch3
var sql3 = #"delete from mytable where id =5;";
nrows = sqlBatchUpdate.RunSql(sql3);
Console.WriteLine("batch3: {0}", nrows);
}
// Alternative: you can run all batches as one
public void RunAllBatches()
{
var sqlBatchUpdate = new SqlBatchUpdate(connstring );
StringBuilder sb = new StringBuilder();
var sql1 = #"update mytable set value2 =1234 where id =4 and Value1>0;";
sb.AppendLine(sql1);
//batch2
var sql2 = #"update mytable set value3 =1234 where id =4 and Value1 =0";
sb.AppendLine(sql2);
//batch3
var sql3 = #"delete from mytable where id =5;";
sb.AppendLine(sql3);
//run all batches
var nrows = c.RunSql(sb.ToString());
Console.WriteLine("all patches: {0}", nrows);
}
}
I simulated that solution and it's working fine with a high performance because all updates /delete run as batch.
I have a IList<T>
IList<Student> studsList = new IList<Student>();
How to load name and age properties from this query to my studsList?
using(OracleCommand command = new OracleCommand(querySQL, connection))
{
connection.Open();
string query = "SELECT name, age FROM Student";
using(OracleCommand command = new OracleCommand(querySQL, connection)) {
using(OracleDataAdapter oracleDataAdapter = new OracleDataAdapter()) {
oracleDataAdapter.SelectCommand = command;
command.ExecuteNonQuery();
//What to do here?
}
}
If you command.ExecuteNonQuery();, you are saying "I don't expect results". If you want to see the results, you'll need to use ExecuteReader, which returns an IDataReader API that allows you to loop over the rows in a forwards direction using .Read(). Then, per row you have access to a range of APIs for accessing columns, including via the indexer ([]), or GetValue or GetValues or typed access methods (GetString, GetInt32, etc).
Or more simply - use a tool like dapper!
var students = connection.Query<Student>(query);
this deals with:
connection lifetime (opening, etc)
creating the command
parameterization (not shown in this example, but really easy)
executing the command
processing the reader
parsing the contents of each row into the T (Student in this case)
closing everything down correctly
For a parameterized example:
var region = "North";
var students = connection.Query<Student>(
"select * from Students where Region=#region", new { region });
(you might need to use $region or :region depending on your ADO.NET provider)
the below coding getting execute but the value was not printing in screen
protected void Button1_Click(object sender, EventArgs e)
{
if (CheckBox1.Checked)
{
string OIMSquery = "SELECT COUNT(name) AS PolicySold FROM TestDate WHERE name='divi'";
SqlCommand OIMScmd = new SqlCommand(OIMSquery, OIMS_01);
OIMS_01.Open();
OIMScmd.ExecuteNonQuery();
OIMS_01.Close();
}
}
You should call ExectueScaler like
int count = (int) OIMScmd.ExecuteScalar();
ExecuteScalar
Executes the query, and returns the first column of the first row in
the result set returned by the query. Additional columns or rows are
ignored.
use ExecuteScalar() to fetch single value
string OIMSquery = "SELECT COUNT(name) AS PolicySold FROM TestDate WHERE name='divi'";
SqlCommand OIMScmd = new SqlCommand(OIMSquery, OIMS_01);
OIMS_01.Open();
int _result = Convert.ToInt32(OIMScmd.ExecuteScalar());
OIMS_01.Close();
ExecuteScalar()
You've got a query. Therefore this call is inappropriate:
OIMScmd.ExecuteNonQuery();
Instead, you should be using ExecuteScalar():
int count = (int) OIMScmd.ExecuteScalar();
(It's possible that it'll return a long rather than an int - I'm not sure offhand.)
Additionally, you should use a using statement for the SqlCommand and create a new connection for each operation:
using (var connection = new SqlConnection(...))
{
connection.Open();
using (var command = new SqlCommand(query, connection))
{
int count = (int) command.ExecuteScalar();
// Now use the count
}
}
It's also not clear what kind of app this is - if it's in a local GUI (WinForms or WPF) you should not be performing database access on the UI thread. The UI will be frozen while the database access occurs. (If this is in a web application, it's even more important that you create a new database connection each time... you don't want two separate requests trying to use the same connection at the same time.)
Use SqlCommand.ExecuteScalar() instead of ExecuteNonQuery() which returns nothing.
You need to use: ExecuteScalar()
Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored.
int result = Convert.ToInt32(OIMScmd.ExecuteScalar());
Instead of
OIMScmd.ExecuteNonQuery();
You can use SqlCommand.ExecuteScalar method;
Executes the query, and returns the first column of the first row in
the result set returned by the query. Additional columns or rows are
ignored.
int i = (int)OIMScmd.ExecuteScalar();
Since this method returns object, you should implicity convert it to int.
using (SqlConnection OIMS_01 = new SqlConnection(connString))
{
SqlCommand OIMScmd = new SqlCommand(OIMSquery, OIMS_01);
try
{
OIMS_01.Open();
int i = (int)OIMScmd.ExecuteScalar();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Try, OIMScmd.ExecuteReader(); method which returns the object of SqlDataReader class then read the value from it.
OR
use, Convert.ToInt32(OIMScmd.ExecuteScalar()) and then print the value.
How to properly do the following update:
using (OracleConnection conn = new OracleConnection())
using (selCmd)
{
string sql1 = "update Table1 set name = joe where id = 10;"
string sql2 = "update Table2 set country = usa where region = americas;"
string sql3 = "update Table3 set weather = sunny where state = CA;"
string sql4 = "update Table4 set engine = v8 where maker = benz;"
cmdUpdate.CommandText = sql(#);
cmdUpdate.Connection = conn;
recs = cmdUpdate.ExecuteNonQuery();
}
I am aware of all or nothing if it's a transaction but I just to see how it works with correct approach.
I'm thinking iterate an array of items [sql1,sql2,sql3,sql4] and pass sql(#) in the CommandText and perform ExecuteNonQuery each time.
If I remember correctly, it is possible to concatenate multiple SQL statements in one string separated by semi-colons (;). Otherwise, there is nothing wrong with executing multiple ExecuteNonQuery() calls.
string sql1 = "BEGIN update Table1 set name = 'joe' where id = 10;",
sql2 = "update Table2 set country = 'usa' where region = 'americas';",
sql3 = "update Table3 set weather = 'sunny' where state = 'CA';",
sql4 = "update Table4 set engine = 'v8' where maker = 'benz'; END;";
string sql = string.Format("{0}{1}{2}{3}",sql1,sql2,sql3,sql4);
using (OracleConnection conn = new OracleConnection())
using (OracleCommand cmdUpdate = new OracleCommand(sql, conn))
{
conn.Open();
recs = cmdUpdate.ExecuteNonQuery();
}
I recently came across this issue in some old code. We dynamically build chain of SQL calls (with support for Oracle and Sql Server). Since there is no current Oracle production implementation, nobody tested Oracle operation and customer bugs are not coming in. I found a code that builds chain of commands and then, for Oracle it uses String.Split(';'). Then, it uses a loop to execute each statement in transaction: rowsAffecter += ExecuteNonQuery....
I don't like this idea because without parameterization it is dangerous approach, since some data can contain ;. But even if parameterization is in place...
... one of the issues of making anonymous block for Oracle ("begin... end;") is that ExecuteNonQuery will not return number of rows (returns -1), which is sometimes needed to judge if something got updated or not.
to solve this issue I've done this
private string AppendOracleCountOrNothing(StringBuilder sql)
{
if (_myProvider == Providers.Oracle)
sql.AppendLine("rowCnt := rowCnt + SQL%ROWCOUNT;");
}
public void SomeMethod()
{
var longSqlChain = new StringBuilder(2000);
longSqlChain.Append("Insert into table...;");
AppendOracleCountOrNothing(longSqlChain);
if (someCondition)
{
longSqlChain.AppendLine("Update anotherTable...;");
AppendOracleCountOrNothing(longSqlChain);
}
// may be, add some more sql to longSqlChain here....
int rowsAffected;
if (_myProvider == Providers.Oracle)
{
longSqlChain.Insert(0, #"DECLARE
rowCnt number(10) := 0
BEGIN
").AppendLine(#":1 := rowCnt;
END;");
// Now, here we have some abstract wrappers that hide provider specific code.
// But the idea is to prepare provider specific output parameter and then parse its value
IDataParameter p = ParameterWrapper.PrepareParameter(":1", 0, ParameterDirection.Output, myProvider); // note IDataParameter
SqlExecWrapper.ExecuteNonQuery(_myProvider, CommandType.Text, sql, new[]{p});
rowsAffected = p.GetParameterValue(); // GetParameterValue is an extension on IDataParameter
}
else // sql server
{
rowsAffected = SqlExecWrapper.ExecuteNonQuery(_myProvider, CommandType.Text, sql, null);
}
}
This way we make one trip to DB and get the return number of rows affected by this call. and queries can be parameterized as well. Again, better to develop abstraction layer, so, you can call something like parameterizer.CreateParameter(10), which will add parameter to collection and generate :1, :2, :3, etc. (oracle) and #1, #2, #3, etc. (sql server), in your sql statement.
Another approach is to create a simple extension method (ExecuteMultipleNonQuery) that simply splits the string on all semicolons and executes each statement in a loop:
public static class DbCommandExtensions {
public static void ExecuteMultipleNonQuery(this IDbCommand dbCommand)
{
var sqlStatementArray = dbCommand.CommandText.Split(new string[] {";"}, StringSplitOptions.RemoveEmptyEntries);
foreach (string sqlStatement in sqlStatementArray)
{
dbCommand.CommandText = sqlStatement;
dbCommand.ExecuteNonQuery();
}
}
}
I have the next code:
private int bla(out int itemsMin, out int purchase)
{
string ID = (Request.QueryString["Ttrsid"] ?? "0").ToString();
{
SqlConnection connection = new SqlConnection("Data Source=*****;Initial Catalog=****;User ID=****;Password=*****;Integrated Security=False;");
string commandtext = "SELECT Min FROM myItems WHERE itemId=#ID";
SqlCommand command = new SqlCommand(commandtext, connection);
connection.Open();
command.Parameters.AddWithValue("#ID", ID); //Adds the ID we got before to the SQL command
itemsMin = (int)command.ExecuteScalar();
string commandtext2 = "SELECT COUNT (*) FROM purchase";
SqlCommand command2 = new SqlCommand(commandtext2, connection);
purchase = (int)command2.ExecuteScalar();
}
return 0;
}
The code is for two labels that i use - one to get the minimum number (itemsMin), and the other is for the count of the purchase.
I'm using the querystring to get the values by the itemid that the user watching on him now.. (from the address bar (for example: items.aspx?Ttrsid=5 so i want to see the minimum number of the Ttrsid = 5).
Everything works fine. when i'm on the Ttrsid = 1 , Ttrsid = 2 - i get what i want, but when i'm enterd to the Ttrsid = 3 and so on - that's give me the error:
System.NullReferenceException
To the line:
itemsMin = (int)command.ExecuteScalar();
.. and it's not null.. the item have all the required fields like Ttrsid = 2 .... so what wrong here?
The next code is the use of the command above:
int i, p; // variable need not be initialized
Console.WriteLine(bla(out i, out p));
if (i < p)
{
haha.Visible = true;
}
else
{
haha2.Visible = true;
}
Console.WriteLine(i);
Console.WriteLine(p);
i = itemsMin , p = purchase .
I'm guessing there is no matching row in the db, so no rows returned. Sanity-check the result from ExecuteScalar - in particular, check it for null before casting to int. It is also possible that the column contains a null, but maybe I'd expect DBNull.Value for that.
Also - use using on all the IDisposable objects here; the connection and command in particular.
I assume below pasted variable is a int type variable
purchase = (int)
Hence you may not be able to convert null values to an integer so try it changing the sql command as below
SELECT isNull(COUNT (*),0) FROM purchase
#Marc I'm really sorry about it
Don't you want to specify a column name next to the min statement? As below
SELECT Min(columnName) FROM myItems WHERE itemId=#ID ?