This method retrieves the entire data.
I'm trying to switch this method to a method that uses a dictionary but it doesn't work very well.
INSERT, UPDATE, and DELETE completed but I'm having trouble with SELECT.
I want convert to the my method like below link source.
https://gist.github.com/thorsman99/e788dd9cce36c26edd9076c9dac288dd
public static List<TestModel> GetList(string id, string subject, string UseYN, string createDate1, string createDate2)
{
using(SQLiteConnection connection = new SQLiteConnection(_connection))
{
connection.Open();
using(SQLiteCommand command = new SQLiteCommand(connection))
{
command.CommandText = #"SELECT ID, Subject, CreateDate, UpdateDate FROM Test";
command.Parameters.Add(new SQLiteParameter(#"ID" , DbType.String) { Value = id });
command.Parameters.Add(new SQLiteParameter(#"Subject" , DbType.String) { Value = subject });
command.Parameters.Add(new SQLiteParameter(#"CreateDate1", DbType.String) { Value = createDate1 });
command.Parameters.Add(new SQLiteParameter(#"CreateDate2", DbType.String) { Value = createDate2 });
SQLiteDataReader reader = command.ExecuteReader();
List<TestModel> list = new List<TestModel>();
while(reader.Read())
{
TestModel item = new TestModel();
item.ID = reader["ID" ].ToString();
item.Subject = reader["Subject" ].ToString();
item.CreateDate = Convert.ToDateTime(reader["CreateDate"]);
item.UpdateDate = Convert.ToDateTime(reader["UpdateDate"]);
list.Add(item);
}
return list;
}
}
}
I moved connection.Open to directly before the.ExecuteReader` Connections should be open for the shortest possible time. I moved the declare of the Dictionary outside the using block and then the return outside also. This again is to close the connection as soon as possible.
I combined the 2 using blocks to one. Just simplifies the code a bit and saves indenting.
For the dictionary, I used "ID" as the key. I assumed that this was the Primary Key and therefore unique.
public Dictionary<string, TestModel> GetTestModelDictionary(string id, string subject, string UseYN, string createDate1, string createDate2)
{
Dictionary<string, TestModel> dict = new Dictionary<string, TestModel>();
using (SQLiteConnection connection = new SQLiteConnection(_connection))
using (SQLiteCommand command = new SQLiteCommand(connection))
{
command.CommandText = #"SELECT ID, Subject, CreateDate, UpdateDate FROM Test";
command.Parameters.Add(new SQLiteParameter(#"ID", DbType.String) { Value = id });
command.Parameters.Add(new SQLiteParameter(#"Subject", DbType.String) { Value = subject });
command.Parameters.Add(new SQLiteParameter(#"CreateDate1", DbType.String) { Value = createDate1 });
command.Parameters.Add(new SQLiteParameter(#"CreateDate2", DbType.String) { Value = createDate2 });
connection.Open();
SQLiteDataReader reader = command.ExecuteReader();
while (reader.Read())
{
TestModel item = new TestModel();
item.ID = reader["ID"].ToString();
item.Subject = reader["Subject"].ToString();
item.CreateDate = Convert.ToDateTime(reader["CreateDate"]);
item.UpdateDate = Convert.ToDateTime(reader["UpdateDate"]);
dict.Add(item.ID, item);
}
}
return dict;
}
Related
ExecuteReader has rows but initially when i hover and look at result view it has question marks for all the rows. Then when i go back in a second time to hover it says enumeration yielded no results. the HasRows is true however. I'm also getting my output parameters fine, but not the result set from the select statement. I'm executing a sql server stored proc. the SP executes fine in management studio. And again i am getting the output parms, just not the result set.
internal static CrossWalk Create(string senderId, string ediType)
{
var connection = ConfigurationManager.ConnectionStrings["caeCustom"];
DbCommand cmd = new SqlCommand();
cmd.CommandText = "emb_edi_xwalk_select";
cmd.CommandType = CommandType.StoredProcedure;
cmd.AddParameter("#pSENDER_ID", senderId);
cmd.AddParameter("#pMAPPING_TYPE", ediType);
var delegateId = new SqlParameter
{
ParameterName = "#pDELEGATE_ID",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
};
cmd.Parameters.Add(delegateId);
var crossWalk = new CrossWalk();
var dbFactory = DbProviderFactories.GetFactory(connection.ProviderName);
using(var sqlConnection =
dbFactory.CreateConnection(connection.ConnectionString))
{
cmd.Connection = sqlConnection;
using (var sqlReader = cmd.ExecuteReader())
{
if (sqlReader.HasRows)
{
while (sqlReader.Read())
{
//Mapping of AAA values will be AAA03~internal_value -->
loop~external_value
//i.e. AAA03~1008 --> 2010EA~51
string key;
string value;
if (sqlReader["element"].ToString() == "AAA03")
{
key = string.Join("~",
sqlReader["element"].ToString(), sqlReader["internal_value"].ToString());
value = string.Join("~", sqlReader["loop"].ToString(),
sqlReader["external_value"].ToString());
}
else
//Normal xwalk mapping will be loop+element~external_value
--> internal_value
//i.e. 2010ANM101~X3 --> 3
{
key = string.Join("~", sqlReader["loop"] +
sqlReader["element"].ToString(), sqlReader["external_value"].ToString());
value = sqlReader["internal_value"].ToString();
}
crossWalk._lookups.Add(key, value);
}
}
sqlReader.Close();
}
crossWalk.DelegateId =
Convert.ToInt32(cmd.Parameters["#pDELEGATE_ID"].Value);
}
return crossWalk;
}
I have a mssql-context-class for easy access to the database. It contains a function for inserting datarows, that looks like this:
public int? Insert(string tableName, Action<SqlParameterCollection> actionSqlParameterCollection)
{
using (var sqlConnection = new SqlConnection(ConnectionString))
{
sqlConnection.Open();
using (var sqlCommand = sqlConnection.CreateCommand())
{
var commandText = $"insert into {tableName} (#columns) output inserted.id values (#values)";
var valueBuilder = new StringBuilder();
var columnBuilder = new StringBuilder();
actionSqlParameterCollection?.Invoke(sqlCommand.Parameters); //Fill the parameters from outside with some values
foreach (SqlParameter parameter in sqlCommand.Parameters)
{
valueBuilder.Append($",#{parameter.ParameterName}");
columnBuilder.Append($",{parameter.ParameterName}");
}
commandText = commandText.Replace("#values", valueBuilder.ToString().Substring(1));
commandText = commandText.Replace("#columns", columnBuilder.ToString().Substring(1));
sqlCommand.CommandText = commandText;
object result = sqlCommand.ExecuteScalar();
return (int?)result;
}
}
}
Calling this would look something like this:
var context = MsSqlContext.CreateFrom("some_connectionstring");
context.Insert("myTable", parameters => {
parameters.AddWithValue("foo_1", "bar_1");
parameters.AddWithValue("foo_2", "bar_2");
});
Now i want to build a generic sql-context-class which can also handle mysql-databases. The insert-function looks like this so far:
public int? Insert(string tableName, Action<IDataParameterCollection> actionParameterCollection)
{
using (var connection = this.CreateConnection())
{
using (var command = connection.CreateCommand())
{
var commandText = $"insert into {tableName} (#field) values (#values)";
var valueBuilder = new StringBuilder();
var columnBuilder = new StringBuilder();
actionParameterCollection?.Invoke(command.Parameters);
foreach (IDbDataParameter parameter in command.Parameters)
{
valueBuilder.Append($",#{parameter.ParameterName}");
columnBuilder.Append($",{parameter.ParameterName}");
}
commandText = commandText.Replace("#values", valueBuilder.ToString().Substring(1));
commandText = commandText.Replace("#columns", columnBuilder.ToString().Substring(1));
command.CommandText = commandText;
object result = command.ExecuteScalar();
return (int?)result;
}
}
}
When i try to call the function it looks like this:
var context = SqlContext.CreateFrom(SqlProvider.MySql, "Server=localhost;Database=4713_demo;Uid=root;Pwd=;");
context.Insert("my_table", parameters =>
{
parameters.Add(?); //It expects an object
});
My Problem is, dont want to do something like
context.Insert("my_table", parameters =>
{
parameters.Add(context.CreateParameter("foo","bar"));
});
I just want to pass the parametername and the parametervalue. the context-class itself is aware of its provider and should create the parameter. How to afford that?
The solution i came up with, is this SqlParameterizer-class.
public class SqlParameterizer
{
private SqlProvider Provider { get; set; }
private List<IDbDataParameter> ParameterList { get; set; }
public SqlParameterizer(SqlProvider sqlProvider)
{
this.Provider = sqlProvider;
this.ParameterList = new List<IDbDataParameter>();
}
public void Add(string parameterName, object parameterValue)
{
switch(this.Provider)
{
case SqlProvider.MsSql:
this.ParameterList.Add(new SqlParameter(parameterName, parameterValue));
break;
case SqlProvider.MySql:
this.ParameterList.Add(new MySqlParameter(parameterName, parameterValue));
break;
case SqlProvider.OracleSql:
throw new Exception($"SqlProvider '{this.Provider}' not supported yet...");
default:
throw new Exception($"Unknown SqlProvider '{this.Provider}'");
}
}
public IDbDataParameter[] GetParameters()
{
return ParameterList.ToArray();
}
}
Using this class will look like this:
var commandText = $"insert into {tableName} (#columns) values (#values)";
var valueBuilder = new StringBuilder();
var columnBuilder = new StringBuilder();
var parameterizer = new SqlParameterizer(this.Provider);
actionValueParameterizer?.Invoke(parameterizer);
foreach(IDbDataParameter parameter in parameterizer.GetParameters())
{
command.Parameters.Add(parameter);
valueBuilder.Append($",#{parameter.ParameterName}");
columnBuilder.Append($",{parameter.ParameterName}");
}
commandText = commandText.Replace("#values", valueBuilder.ToString().Substring(1));
commandText = commandText.Replace("#columns", columnBuilder.ToString().Substring(1));
command.CommandText = commandText;
command.ExecuteNonQuery();
Calling my insert-function:
context.Insert("some_table", parameterizer =>
{
parameterizer.Add("some_column", "some_value");
});
I am trying to doing this:
Read a row from an SQLite db (in GetRuleByID() method)
Update the same row that I just read during (1) (See UpdateStatusForRuleID() method)
However my problem is that SQLite locks the database after the SELECT in GetRuleByID() so that update in UpdateStatusForRuleID() is only successful when called the first time.
I have tried enabling Write-Ahead-Logging in SQLite as well as PRAGMA read_uncommitted=1 in order to avoid SQLite locking the database for the SELECT, but this does not appear to work.
This should be simple but I have so far spent a complete night trying to solve this... Please help !
private static MicroRuleEngine.Rule GetRuleByID(int ruleID, SQLiteConnection connection, out Dictionary<string, string> dict)
{
dict = new Dictionary<string, string>();
string sql = String.Format("select * from rules WHERE ID = {0} ", ruleID.ToString());
SQLiteCommand command = new SQLiteCommand(sql, connection);
SQLiteDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
// Convert row into a dictionary
for (int lp = 0; lp < reader.FieldCount; lp++)
{
dict.Add(reader.GetName(lp), reader.GetValue(lp) as string);
}
string json = dict["fulljson"];
MicroRuleEngine.Rule r = Newtonsoft.Json.JsonConvert.DeserializeObject<MicroRuleEngine.Rule>(json);
//command.Dispose();
return r;
}
}
internal static void UpdateStatusForRuleID(SQLConnectionManager DBMANAGER, int ruleID, bool status)
{
Dictionary<string, string> dict = null;
string dbVal = (status) ? "1" : "0";
MicroRuleEngine.Rule r = null;
string newJSON = null;
using (SQLiteConnection connection = DBMANAGER.CreateConnection())
{
r = GetRuleByID(ruleID, connection, out dict);
r.Active = (status);
newJSON = Newtonsoft.Json.JsonConvert.SerializeObject(r);
Thread.Sleep(1000);
string sql = "UPDATE rules SET active = #a, fulljson=#j WHERE ID = #i";
using (var command = new SQLiteCommand(sql, connection))
{
command.Parameters.Add(new SQLiteParameter("#a", dbVal));
command.Parameters.Add(new SQLiteParameter("#i", ruleID));
command.Parameters.Add(new SQLiteParameter("#j", newJSON));
command.ExecuteNonQuery(); // Database is locked here ???
}
connection.Close();
}
}
"Database is locked" means that some other connection (in the same or some other process) still has an active transaction.
You don't need multiple connections (unless you are using multiple threads); just use a single connection object for all database accesses.
Ensure that all command, reader, and transaction objects (and connections, if you decide to use temporary ones) are properly cleaned up, by using using:
using (var command = new SQLiteCommand(sql, connection))
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
...
}
Apparently, the code below works. I basically dropped the GetRuleByID() method (but then I had to re-write 4 other methods)
Thanks to all who provided input.
internal static void UpdateStatusForRuleID(SQLConnectionManager DBMANAGER, int ruleID, bool status)
{
string dbVal = (status) ? "1" : "0";
MicroRuleEngine.Rule r = null;
string newJSON = null;
using (SQLiteConnection conn = DBMANAGER.CreateConnection())
{
string sql = String.Format("select * from rules WHERE ID = {0} ", ruleID.ToString());
using (var command = new SQLiteCommand(sql, conn))
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
reader.Read();
string json = reader["fulljson"].ToString();
r = Newtonsoft.Json.JsonConvert.DeserializeObject<MicroRuleEngine.Rule>(json);
r.Active = (status);
newJSON = Newtonsoft.Json.JsonConvert.SerializeObject(r);
string sql2 = "UPDATE rules SET active = #a, fulljson=#j WHERE ID = #i";
using (var command2 = new SQLiteCommand(sql2, conn))
{
command2.Parameters.Add(new SQLiteParameter("#a", dbVal));
command2.Parameters.Add(new SQLiteParameter("#i", ruleID));
command2.Parameters.Add(new SQLiteParameter("#j", newJSON));
command2.ExecuteNonQuery();
}
}
}
}
}
I have a StoredProcedure that I created like this;
CREATE DEFINER=`mysqladmin`#`%` PROCEDURE `Alerts_GetAlerts`(IN managerID INT)
BEGIN
SELECT ID, Type, EmpID, ManagerID, HolID
FROM Alerts
WHERE ManagerID = managerID;
END$$
I then try to call this from my C# code like so;
using (var con = new MySqlConnection(MySQLConStr))
{
con.Open();
using (MySqlCommand cmd = new MySqlCommand("Alerts_GetAlerts", con))
{
cmd.Parameters.AddWithValue("#managerID", managerID);
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var alert = new AlertsModel
{
ID = Convert.ToInt32(dataReader[0]),
Type = Convert.ToInt32(dataReader[1]),
ManagerID = Convert.ToInt32(dataReader[2]),
EmployeeID = Convert.ToInt32(dataReader[3]),
HolidayID = Convert.ToInt32(dataReader[4]),
};
AllAlerts.Add(alert);
}
}
}
return AllAlerts;
}
However I constantly get, Incorrect number of arguments for PROCEDURE sdcdatabase.Alerts_GetAlerts; expected 1, got 0 even though to me it appears I am passing the managerID argument through;
cmd.Parameters.AddWithValue("#managerID", managerID);
Where am I going wrong?
try this code
using (var con = new MySqlConnection(MySQLConStr))
{
con.Open();
using (MySqlCommand cmd = new MySqlCommand("Alerts_GetAlerts(#managerID)", con))
{
cmd.Parameters.AddWithValue("#managerID", managerID);
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var alert = new AlertsModel
{
ID = Convert.ToInt32(dataReader[0]),
Type = Convert.ToInt32(dataReader[1]),
ManagerID = Convert.ToInt32(dataReader[2]),
EmployeeID = Convert.ToInt32(dataReader[3]),
HolidayID = Convert.ToInt32(dataReader[4]),
};
AllAlerts.Add(alert);
}
}
}
return AllAlerts;
}
I am trying to read values form my database. But why am I getting only values with no column name?
this is my controller. that returns the values in JSON
SqlCommand cmd = con.CreateCommand();
cmd.CommandText = "SELECT DISTINCT State FROM MyDBtable";
con.Open();
List<string> StateList = new List<string>();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
StateList.Add(reader[0].ToString());
}
return Json(new
{
myTable = StateList
}, JsonRequestBehavior.AllowGet);
and this is my JSON
{"myTable":["VA","CA"]}
Where as, it's suppose to give me
{"myTable":[{"State":"VA"},{"State":"CA"}]}
Why is it not reading and printing State
String does not have property "State". Create anonymous type instead:
myTable = StateList.Select(s => new { State = s })
UPDATE: Easiest solution for multiple columns - create a DTO for that
public class MyItem // of course use more descriptive name
{
public string State { get; set; }
public string Capital { get; set; }
// etc
}
And fill it from reader:
List<MyItem> items = new List<MyItem>();
while (reader.Read())
{
MyItem item = new MyItem();
item.State = reader[0].ToString();
item.Capital = reader[1].ToString();
// etc
items.Add(item);
}
return Json(new { myTable = items }, JsonRequestBehavior.AllowGet);
One more sample (with Dapper, which you can find on NuGet). Add using Dapper; to your code. Use same DTO class, as above.
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
return Json(new {
myTable = connection.Query<MyItem>("SELECT * FROM MyDBtable").ToList()
}, JsonRequestBehavior.AllowGet);
}
Because you are selecting state. This will create a new object where the State property is assigned the state, such that you get what you want:
SqlCommand cmd = con.CreateCommand();
cmd.CommandText = "SELECT DISTINCT State FROM MyDBtable";
con.Open();
List<string> StateList = new List<string>();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
StateList.Add(reader[0].ToString());
}
return Json(new
{
myTable = StateList.Select(i => new { State = i })
}, JsonRequestBehavior.AllowGet);
For additional columns, see lazyberezovsky's answer who has changed StateList to solve this.