I'm querying a database with SQLite. I'm trying to get data from this database, save it in an array, then return to the controller. I need to present this data using a foreach loop in my view.
string sql = "select * from Tasks Where UserId = " + userId.ToString();
using (SQLiteConnection conn = new SQLiteConnection(connString))
{
SQLiteCommand cmd = new SQLiteCommand(sql, conn);
conn.Open();
using (SQLiteDataReader rdr = cmd.ExecuteReader())
{
int i = 0;
while (rdr.Read())
{
//here is what i would do in PHP
$array[$i]['name'] = $rdr[i]["name"];
$array[$i]['key'] $rdr[$i]["key"];
}
}
}
return array;
Firstly, learn to use parameterised queries not string concatenation as this will help prevent SQL injection attacks.
string sql = "select * from Tasks Where UserId = #userId";
Also, if you create a class to represent a record in your Tasks table then you can build instances of this object and return them in a list which will make using the code easier since instead of having untyped arrays, you will have an object with properties (in your view you can do foreach (var task in Model) where Model is a List<Task>.
public class Task
{
public int Id { get; set; }
public string Name { get; set; }
public string Key { get; set; }
}
var tasks = new List<Task>(); // create a list to populate with tasks
using (var connection = new SQLiteConnection(connString))
{
var command = new SQLiteCommand(sql, conn);
command.Parameters.Add("#userId", userId); // only do .ToString on userId if the column is a string.
connection.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var task = new Task();
task.Id = (int)reader["id"]; // the name of the column in the reader will match the column in the Tasks table.
task.Name = (string)reader["name"];
task.Key = (string)reader["key"];
tasks.Add(task);
}
}
}
return tasks;
Instead of writing all the query logic and creating objects, you can use frameworks called Object Relational Mappers (ORM) to do this for you. There are a number of them around, some more simple than others. A MicroORM may suit your purposes, they are simple and easy to use (I have built one called MicroLite but there are others such as dapper or PetaPoco. If you want something more powerful, NHibernate and Entity Framework are popular choices.
Related
I am trying to display two SQL tables from the same DB in a webpage but the code below is displaying the 'BottomStock' table twice and everything I try seems to either get part of the data from the 'TopStock' table or none at all. I have scroll through countless forums but I have been unable to find a suitable solution. Any help would be appreciated.
public class Test4Model : PageModel
{
public List<FreezerInfo> listTopFreezer = new List<FreezerInfo>();
public List<FreezerInfo> listBottomFreezer = new List<FreezerInfo>();
public void OnGet()
{
try
{
using (var connection = new SqlConnection("Data Source=SDS-
LAPTOP\\SQLEXPRESS;Initial Catalog=test;user id=sa;password=wis09"))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT * FROM TopStock";
command.ExecuteNonQuery();
command.CommandText = "SELECT * FROM BottomStock";
command.ExecuteNonQuery();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
//reader.Read();
{
FreezerInfo TopStock = new FreezerInfo();
TopStock.Description = reader.GetString(1);
TopStock.Quantity = reader.GetString(2);
listTopFreezer.Add(TopStock);
FreezerInfo BottomStock = new FreezerInfo();
BottomStock.Description = reader.GetString(1);
BottomStock.Quantity = reader.GetString(2);
listBottomFreezer.Add(BottomStock);
}
}
}
}
}
catch (Exception ex)
{
}
}
}
public class FreezerInfo
{
public string Description { get; set; }
public string Quantity { get; set; }
}
You are using SqlCommand completely wrong. ExecuteNonQuery does not return results. Only ExecuteScalar or ExecuteReader do. Furthermore, you have two batches each with a SELECT, but you are only executing one and somehow expecting the results to be interleaved.
I would advise you to use one batch of two SELECT statements, you can use NextResult to move to the next resultset within the batch.
Store your connection string in a settings file, not hard-coded.
Only select the columns you need, rather than SELECT *.
Use column names rather than ordinals, especially if you are using SELECT *.
Do not swallow exceptions. Handle them or allow them to bubble back to the caller.
Consider using async to allow the caller to continue asynchronously.
Reconsider the data types of the columns.
Consider why you have two almost identical tables in the first place. Perhaps they should be merged.
public void OnGet()
{
try
{
const string query = #"
SELECT Description, Quantity
FROM TopStock;
SELECT Description, Quantity
FROM BottomStock;
";
using var connection = new SqlConnection(connectionStringFromSettingsFileHere);
using var command = new SqlCommand(query, connection);
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
while (reader.Read())
{
FreezerInfo TopStock = new FreezerInfo
{
Description = (string)reader["Description"],
Quantity = (string)reader["Quantity"], // shouldn't it be an int???
};
listTopFreezer.Add(TopStock);
}
reader.NextResult();
while (reader.Read())
{
FreezerInfo BottomStock = new FreezerInfo
{
Description = (string)reader["Description"];
Quantity = (string)reader["Quantity"], // shouldn't it be an int???
};
listBottomFreezer.Add(BottomStock);
}
}
catch (Exception ex)
{
// exception handling here. DO NOT SWALLOW
}
}
I want an efficient way to retrieve all info that shares the same Foreign key in a table, and store the data in list/array.
I can read several rows from one column:
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
int idForeignKey = inputIdFkey //Implemented on the WebPage for testing purposes
List<string> result = new List<string>();
string oString = "Select Column from Table where foreignKey = #fKey";
conn.Open();
SqlCommand oCmd = new SqlCommand(oString, conn);
oCmd.Parameters.AddWithValue("#fKey", idForeignKey);
using (SqlDataReader oReader = oCmd.ExecuteReader())
{
while (oReader.Read())
{
result.Add(oReader.GetString(0));
}
}
conn.Close();
And I can read several columns if I am targeting one specific row:
int sqlData1;
int sqlData2;
int sqlData3;
string oString = "Select * from Table where TableID = #tId";
SqlCommand oCmd = new SqlCommand(oString, conn);
oCmd.Parameters.AddWithValue("#tId", 1001);
conn.Open();
using (SqlDataReader oReader = oCmd.ExecuteReader())
{
while (oReader.Read())
{
sqlData1 = oReader["Row1"].ToString();
sqlData2 = oReader["Row2"].ToString();
sqlData3 = oReader["Row3"].ToString();
}
}
conn.Close();
But I would love to be able to read all/specific data that has the same foreign key. So I want to be able to retrieve several rows, save them to a list, and retrieve several other row data from a different column that shares the same foreign key.
I imagine it to be something like this:
int idForeignKey = inputIdFkey //Implemented on the WebPage for testing purposes
List<int> intList = new List<int>();
List<string> stringList = new List<string>();
List<DateTime> dateList = new List<DateTime>();
string oString = "Select * from Table where ForeignKey = #fKey";
conn.Open();
SqlCommand oCmdSleep = new SqlCommand(oString, conn);
oCmdSleep.Parameters.AddWithValue("#fKey", idForeignKey);
using (SqlDataReader oReader = oCmdSleep.ExecuteReader())
{
while (oReader.Read())
{
intList.Add(oReader["Column1"].GetDateTime(0));
dstringList.Add(oReader["Column3"].GetDateTime(0));
dateList.Add(oReader["Column4"].GetDateTime(0));
}
}
conn.Close();
But this does not working... Please advice me
If you use something like Dapper it would simplify mapping your query result to List<T>.
Add Dapper to your project using nuget.
Install-Package Dapper -Version 1.50.5
Add using for Dapper at the top of your class where you are running the query.
using Dapper;
Add a class that matches the structure of your query results. There are ways to do this using a script or utility app. Here's an app for that.
public class MyClass
{
public int MyId { get; set; }
public string MyName { get; set; }
public DateTime MyDateTime { get; set; }
}
Then where you run the query do so like this.
using (conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
{
var MyList = conn.Query<MyClass>(#"select * from Table where ForeignKey = #fKey",
new { fKey = "SomeKey" }).ToList();
}
Once the query runs, you can then iterate across MyList.
foreach (var myItem in MyList)
{
// Do something with myItem
}
If you want to bind the results, simply remove .ToList() from the end of the query as its default is IObservable<T>.
No need to give the name of the column, simply use the already established index:
int idForeignKey = inputIdFkey //Implemented on the WebPage for testing purposes
List<int> intList = new List<int>();
List<string> stringList = new List<string>();
List<DateTime> dateList = new List<DateTime>();
string oString = "Select * from Table where ForeignKey = #fKey";
conn.Open();
SqlCommand oCmdSleep = new SqlCommand(oString, conn);
oCmdSleep.Parameters.AddWithValue("#fKey", idForeignKey);
using (SqlDataReader oReader = oCmdSleep.ExecuteReader())
{
while (oReader.Read())
{
intList.Add(oReader.GetDateTime(0));
dstringList.Add(oReader.GetDateTime(3));
dateList.Add(oReader.GetDateTime(4));
}
}
conn.Close();
Here is a breakdown:
listVariable.Add(oReader.GetDataType("Index of column"));
This way you get to retrieve all row data for shared foreign key and the option to do so for as many columns as you want.
I am using the following Access query on C# MVC to compare two tables and return records that fall within the date range and machine selected by the user. The query code runs perfectly on the actual Access database but I guess something is wrong with the connection string and the code to return the results. I am not sure what's wrong with the code and I would appreciate if someone could help me determine what's wrong. Thanks!
C# MVC Controller code:
public ActionResult MissingChecksheets(string startDate, string endDate, string machine)
{
var query = $#"SELECT * FROM [TrackingLog]
WHERE [TrackingLog].[Workcenter] = '{machine}' AND
[TrackingLog].[Complete Date] > #{startDate}# AND
[TrackingLog].[Complete Date] < #{endDate}# AND
[TrackingLog].[Order Item] NOT IN (SELECT [OrderNum] FROM [dbo_Checksheet])";
var sheets = new List<Checksheet>();
using (var con = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Checksheets.accdb;"))
{
using (var command = new OleDbCommand(query, con))
{
con.Open();
using (var reader = command.ExecuteReader())
{
while (reader.NextResult())
{
sheets.Add(new FabChecksheet
{
OrderNum = reader.GetString(0),
PartNum = reader.GetString(1)
});
}
}
}
}
return PartialView(sheets);
}
OleDbDataReader.NextResult() method used to move between result set if the query string has multiple result sets (e.g. more than two SELECT statements, not counting SELECT inside aggregate functions). Since your query has single result set, OleDbDataReader.Read() must be used to move between records:
using (var con = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Checksheets.accdb;"))
{
using (var command = new OleDbCommand(query, con))
{
con.Open();
using (var reader = command.ExecuteReader())
{
// Only single result set, use 'Read' here
while (reader.Read())
{
sheets.Add(new FabChecksheet
{
OrderNum = reader.GetString(0),
PartNum = reader.GetString(1)
});
}
}
}
}
Note that if NextResult() is used, the data reader will move to next result set which has empty data.
Related issue:
Difference between SqlDataReader.Read and SqlDataReader.NextResult
Is the code below implementing the secure way to retrieve the data from database?
help me please, I don't understand about SQL Injection. Someone told me this code can easily get injected. If yes, can somebody explain it? Thank you.
public int CheckID(string column, string table, string wheres)
{
int i = 0;
sqlcon = ConnectToMain();
string sqlquery = "SELECT "+column+" FROM "+table+" "+wheres+"";
using (sqlcon)
{
sqlcon.Open();
SqlCommand sqlcom = new SqlCommand(sqlquery, sqlcon);
using (sqlcom)
{
SqlDataReader dr = sqlcom.ExecuteReader();
dr.Read();
if (dr.HasRows)
{
i = dr.GetInt32(0);
}
else
{
i = 0;
}
}
sqlcon.Close();
}
return i;
}
This code has far too many problems.
Table, column and criteria are passed as strings and concatenated, which means that the code is prone to SQL injection.
Database details like table, column criteria are spilled into the function's caller. Are you going to use this method to query anything other than a Visitor table?
A reader is used when only a single value is wanted.
The connection is created outside the using block and stored in a field.
This is definitelly a memory leak and probably a connection leak as well. Just create the connection locally.
A simple command call fixes all of these problems:
public int CheckIDVisitor(visitorName)
{
string query = "SELECT ID FROM Visitors where Name=#name";
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
using( var cmd=new SqlCommand(query,sqlConn))
{
var cmdParam=cmd.Parameters.Add("#name",SqlDbType.NVarChar,20);
cmdParam.Value=visitorName;
sqlConn.Open();
var result=(int?)cmd.ExecuteScalar();
return result??0;
}
}
You could also create the command in advance and store it in a field. You can attach the connection to the command each time you want to execute it:
public void InitVisitorCommand()
{
string query = "SELECT ID FROM Visitors where Name=#name";
var cmd=new SqlCommand(query,sqlConn);
var cmdParam=cmd.Parameters.Add("#name",SqlDbType.NVarChar,20);
_myVisitorCommand=cmd;
}
...
public int CheckIDVisitor(visitorName)
{
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
{
_myVisitorCommand.Parameters.["#name"]Value=visitorName;
_myVisitorCommand.Connection=sqlConn;
sqlConn.Open();
var result=(int?)cmd.ExecuteScalar();
return result??0;
}
}
An even better option would be to use a micro-ORM like Dapper.Net to get rid of all this code:
public int CheckIDVisitor(visitorName)
{
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
{
string sql = "SELECT ID FROM Visitors WHERE name=#name"
var result = conn.Query<int?>(sql, new { name = visitorName);
return result??0;
}
}
Or
public int[] CheckIDVisitors(string []visitors)
{
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
{
string sql = "SELECT ID FROM Visitors WHERE name IN #names"
var results = conn.Query<int?>(sql, new { names = visitors);
return results.ToArray();
}
}
Trying to communicate with the database, I am little bit confused about how to pass a value as a parameter(for ex. an itemID) and get back the records that are having this ID.
Here is my stored procedure:
ALTER PROCEDURE [dbo].[sp_lightItem]
(
#itemID INT
)
AS
BEGIN
SELECT [itemID],
[itemName],
[itemLocation],
[itemChBy]
FROM [dbo].[Item]
WHERE itemSystemType='E' and itemID=#itemID ORDER BY itemID DESC;
END
And this is my c# code so far..
public string LoadItemNew(int ItemID)
{
var acf = new AcFunctions();
var newstorevalue = SqlHelper.ExecuteDataset(acf.AcConn(), "sp_lightItem", ItemID);
}
As you can see in stored procedure, what I want is to get back those 4 elements:
[itemID],[itemName],[itemLocation],[itemChBy]
Unfortunately I do not know how to get them back/how to call them in c# function.
Any help is welcome.
i dont have enough repo to comment , can you provide the definition of
AcFunctions();
i am sure you it must be returning ConnectionString
try this
public string LoadItemNew(int ItemID)
{
var acf = new AcFunctions();
var newstorevalue = SqlHelper.ExecuteDataset(acf.AcConn(), "sp_lightItem", new SqlParameter ("#itemID",ItemID));
}
Edit 1
try this
public string LoadItemNew(int ItemID)
{
List<string> testList = new List<string>();
var acf = new AcFunctions();
var newstorevalue = SqlHelper.ExecuteReader(acf.AcConn(), "sp_lightItem", new SqlParameter ("#itemID",ItemID));
if(newstorevalue.HasRows)
{
while(newstorevalue.Read())
{
testList.Add(newstorevalue["itemID"].ToString());
testList.Add(newstorevalue["itemName"].ToString());
testList.Add(newstorevalue["itemLocation"].ToString());
testList.Add(newstorevalue["itemChBy"].ToString());
}
}
}
You can try with this approach, I will use Data Transfer Object for holding data retrieved from database and Execute DataReader for reading.
First of all, you need to create a DTO class, I will call it LightItemDTO
public class LightItemDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public string ChangedBy { get; set; }
}
Note: How to know the type of properties, you can reference this link: SQL Server Data Type Mappings
And now, I will using ADO.NET for execute the stored procedure to get data from database
public IEnumerable<LightItemDTO> GetLightItem(string itemText, string sqlConnectionString)
{
var results = new List<LightItemDTO>();
using (var con = new SqlConnection(sqlConnectionString))
{
using (var cmd = new SqlCommand("sp_lightItem", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#ItemID", SqlDbType.VarChar).Value = itemText;
con.Open();
using (var reader = cmd.ExecuteReader())
{
results.Add(new LightItemDTO
{
Id = Convert.ToInt32(reader["itemID"]),
Name = reader["itemName"].ToString(),
Location = reader["itemLocation"].ToString(),
ChangedBy = reader["itemChBy"].ToString()
});
}
}
}
return results;
}
Using DataReader is the best practice with high performance.
ADO.NET is the manual way to achieve this task, you can use some ORM framework for do it easier, such as: Entity Framework, Dapper.NET ...
You could execute stored procedure with parameters in following:
using (SqlConnection con = new SqlConnection(dc.Con)) {
using (SqlCommand cmd = new SqlCommand("sp_lightItem", con)) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#ItemID", SqlDbType.VarChar).Value = itemId.Text;
con.Open();
cmd.ExecuteNonQuery();
}
}
first of all set the Commandtype as stored procedure and that procedure will return some data which you will save in dataset and then return the data set to where ever you want to populate the data
public DataSet LoadItemNew(int ItemID)
{
var acf = new AcFunctions();
return DataSet ds = SqlHelper.ExecuteDataset(acf.AcConn(),CommandType.StoredProcedure, "sp_lightItem",new SqlParameter("#itemID" ItemID);
}
You can try like this ..
public string LoadItemNew(int ItemID)
{
var acf = new AcFunctions();
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("#itemID", ItemID));
DataSet Ds = SqlHelper.ExecuteDataset(acf.AcConn(),CommandType.StoredProcedure, "sp_lightItem" , parameters.ToArray());
return "ok";
}