I have the following code to query the database, get the data, set each property for the object, and then return that object. In this case, it is an impact object that has the properties FinancialImpactId and EffectiveDate.
Throughout my project I query the database several times like the example below using objects that have the associated property name in the database.
Is there a way to create a generic reusable class that would take in the following parameters: object to be returned or the type, the connection, and the query text?
It would then return the object and set each property like below but dynamically and would be reusable.
try
{
using (var conn = new SqlConnection())
{
conn.ConnectionString = connection.ConnectionString;
conn.Open();
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = " SELECT TOP 1 fi.financialimpactid,
fi.effectivedate " +
" FROM FinancialImpact fi " +
" INNER JOIN [Case] c ON c.caseid =
fi.caseid " +
" WHERE fi.isDeleted = 0 AND
c.IsDeleted = 0";
var reader = cmd.ExecuteReader();
if (reader.HasRows)
{
reader.Read();
impact.FinancialImpactId = reader.GetInt32(0);
impact.EffectiveDate = reader.GetDateTime(1);
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
An ORM like Entity Framework is certainly the better option here, but you can absolutely create a class that could query the database this way. You won't have parametrised queries or transactions, and you really won't save that much in re-usability, since every time you call it you'd need to provide lots of code to get it to function in the first place.
An example. Take a query class:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace QueryExample {
public class Query {
private readonly SqlConnection _connection;
public Query(SqlConnection connection) {
_connection = connection;
}
public IEnumerable<T> Run<T>(string text, Func<SqlDataReader, T> builder) {
var results = new List<T>();
try {
_connection.Open();
using (var command = new SqlCommand()) {
command.Connection = _connection;
command.CommandType = CommandType.Text;
command.CommandText = text;
var reader = command.ExecuteReader();
if (reader.HasRows)
while (reader.Read())
results.Add(builder(reader));
}
_connection.Close();
} catch (Exception e) {
Console.WriteLine(e.Message);
}
return results;
}
}
}
...now take an example of running this class:
using System;
using System.Data.SqlClient;
namespace QueryExample {
public class Example {
public static void Run() {
using (var connection = new SqlConnection(string.Empty)) {
var query = new Query(connection);
const string text = "SELECT TOP 1 fi.financialimpactid, " +
"fi.effectivedate " +
" FROM FinancialImpact fi " +
" INNER JOIN [Case] c ON c.caseid = " +
"fi.caseid " +
" WHERE fi.isDeleted = 0 AND " +
"c.IsDeleted = 0";
var results = query.Run(text, GetData);
// do something with the results
}
}
private static Data GetData(SqlDataReader reader) => new Data {
FinancialImpactId = reader.GetInt32(0),
EffectiveDate = reader.GetDateTime(1)
};
}
internal class Data {
public int FinancialImpactId { get; set; }
public DateTime EffectiveDate { get; set; }
}
}
You see how running the class doesn't give you that much usability, especially since you will always have to return a result (making INSERT INTO, UPDATE and DELETE "result builders" more difficult to define), and it always has to be a list (not as big of a problem, but always having to do an empty check and accessing the first record can get old fast)?
This is one reason why you'd use an ORM nowadays. They not only simplify query creation, but also make parametrisation unnecessary.
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'm trying to get all data from an SQL table and store it in a List using the C# programming language.
the SQL statement I'm using is:
private string cmdShowEmployees = "SELECT * FROM Employees;";
This is being used in the same class as a function
public List<string> showAllIdData()
{
List<string> id = new List<string>();
using (sqlConnection = getSqlConnection())
{
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandText = cmdShowEmployees;
SqlDataReader reader = sqlCommand.ExecuteReader();
while (reader.Read()) {
id.Add(reader[0].ToString());
}
return id;
}
}
and here
public List<string> showAllActiveData()
{
List<string> active = new List<string>();
using (sqlConnection = getSqlConnection())
{
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandText = cmdShowEmployees;
SqlDataReader reader = sqlCommand.ExecuteReader();
while (reader.Read()) {
active.Add(reader[1].ToString());
}
return active;
}
I would have to create 9 more functions this way in order to get all the data out of the Employees table. This seems very inefficient and I was wondering if there was a more elegant way to do this.
I know using an adapter is one way to do it but I don't think it is possible to convert a filled adapter to a list, list list etc.
SqlDataAdapter adapter = sqlDataCollection.getAdapter();
DataSet dataset = new DataSet();
adapter.Fill(dataset, "idEmployees");
dataGridView1.DataSource = dataset;
dataGridView1.DataMember = "idEmployees";
Any ideas?
If you must use the reader in this way, why not create an object which holds the table row data.
public class SomeComplexItem
{
public string SomeColumnValue { get; set;}
public string SomeColumnValue2 { get; set;}
public string SomeColumnValue3 { get; set;}
public string SomeColumnValue4 { get; set;}
}
That way you can loop through with your reader as follows:
public List<SomeComplexItem> showAllActiveData()
{
List<SomeComplexItem> active = new List<SomeComplexItem>();
using (sqlConnection = getSqlConnection())
{
sqlCommand.Connection = sqlConnection;
sqlCommand.CommandText = cmdShowEmployees;
SqlDataReader reader = sqlCommand.ExecuteReader();
while (reader.Read())
{
var someComplexItem = new SomeComplexItem();
someComplexItem.SomeColumnValue = reader[1].ToString();
someComplexItem.SomeColumnValue2 = reader[2].ToString();
someComplexItem.SomeColumnValue3 = reader[3].ToString();
active.Add(someComplexItem);
}
return active;
}
You could use two select statements to populate two List<string> as shown in the example below where the key between reads is reader.NextResult();.
The database used is the standard Microsoft NorthWind database.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
namespace SQL_Server_TwoList
{
public class DataOperations
{
public List<string> Titles { get; set; }
public List<string> Names { get; set; }
/// <summary>
/// Trigger code to load two list above
/// </summary>
public DataOperations()
{
Titles = new List<string>();
Names = new List<string>();
}
public bool LoadData()
{
try
{
using (SqlConnection cn = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
string commandText = #"
SELECT [TitleOfCourtesy] + ' ' + [LastName] + ' ' + [FirstName] As FullName FROM [NORTHWND.MDF].[dbo].[Employees];
SELECT DISTINCT [Title] FROM [NORTHWND.MDF].[dbo].[Employees];";
using (SqlCommand cmd = new SqlCommand(commandText, cn))
{
cn.Open();
SqlDataReader reader = cmd.ExecuteReader();
// get results into first list from first select
if (reader.HasRows)
{
while (reader.Read())
{
Names.Add(reader.GetString(0));
}
// move on to second select
reader.NextResult();
// get results into first list from first select
if (reader.HasRows)
{
while (reader.Read())
{
Titles.Add(reader.GetString(0));
}
}
}
}
}
return true;
}
catch (Exception)
{
return false;
}
}
}
}
Form code
namespace SQL_Server_TwoList
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
DataOperations dataOps = new DataOperations();
if (dataOps.LoadData())
{
listBox1.DataSource = dataOps.Names;
listBox2.DataSource = dataOps.Titles;
}
}
}
}
You could always add it all to a dataset or datatable instead of looping through using datareader to add to an array, dataset allows you to access data in similar way to array anyway.
Connstr = "Data Source = " + SelectedIP + "; Initial Catalog = " + dbName + "; User ID = " + txtUsername.Text +"; Password = "+ txtPassword.Text +"";
conn = new SqlConnection(Connstr);
try
{
string contents = "SELECT * FROM ..."
conn.Open();
SqlDataAdapter da_1 = new SqlDataAdapter(contents, conn); //create command using contents of sql file
da_1.SelectCommand.CommandTimeout = 120; //set timeout in seconds
DataSet ds_1 = new DataSet(); //create dataset to hold any errors that are rturned from the database
try
{
//manipulate database
da_1.Fill(ds_1);
if (ds_1.Tables[0].Rows.Count > 0) //loop through all rows of dataset
{
for (int i = 0; i < ds_1.Tables[0].Rows.Count; i++)
{
//rows[rownumber][column number/ "columnName"]
Console.Write(ds_1.Tables[0].Rows[i][0].ToString() + " ");
}
}
}
catch(Exception err)
{}
conn.Close();
}
catch(Exception ex)
{}
Im very new on C#
I Only create 1 Form that Can insert Data to Mysql Database. My code not have Error, but data cant enter the Database. I m so confused.
this my code
Koneksi.cs
using System;
using System.Data;
using MySql.Data.MySqlClient;
using System.Drawing;
using System.Windows.Forms;
namespace timbangan
{
public class Koneksi
{
public MySqlConnection konek;
//string konfigKoneksi = "server=localhost; database=timbangan; uid=root; pwd=";
string konfigKoneksi = "Server=localhost;Database=timbangan;Uid=root;Pwd=";
public void bukaKoneksi()
{
konek = new MySqlConnection(konfigKoneksi);
konek.Open();
var temp = konek.State.ToString();
if (temp == "Open")
{
MessageBox.Show(#"Connection working.");
}
else {
MessageBox.Show(#"Please check connection string");
}
}
public void tutupKoneksi()
{
konek = new MySqlConnection(konfigKoneksi);
konek.Close();
}
}//end of koneksi
}//end namespace
Isidata.cs File
using System;
using System.Data;
using MySql.Data.MySqlClient;
using System.Windows.Forms;
namespace timbangan
{
public class Isidata
{
MySqlDataAdapter adapter;
MySqlCommand komand;
Koneksi classKoneksi;
DataTable tabel;
string sql = "";
public DataTable tambahData(string berat_filter, string qty, string nama_barang, string dari, string shift)
{
classKoneksi = new Koneksi();
sql = "insert into tb_timbang(BERAT_FILTER,QTY,NAMA_BARANG,DARI,SHIFT) values (" + berat_filter + ",'" + qty + "','" + nama_barang + "','" + dari + "','" + shift + "')";
//MessageBox.Show(sql);
tabel = new DataTable();
try
{
classKoneksi.bukaKoneksi();
komand = new MySqlCommand(sql);
adapter = new MySqlDataAdapter(sql, classKoneksi.konek);
adapter.Fill(tabel);
}
catch (Exception)
{
MessageBox.Show("error");
}
return tabel;
}
}//end of issdata
}//end of timbangan
Form1.cs File
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Data;
namespace timbangan
{
public partial class Form1 : Form
{
public DataTable tabel;
public string status = "";
public string berat_filter, qty, nama_barang, dari, shift;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Isidata isi = new Isidata();
tabel = isi.tambahData(tbBerat.Text, tbQty.Text, tbNama.Text, tbDari.Text, tbShift.Text);
MessageBox.Show("Berhasil");
}
}
}
Can Anyone Help me to Fix this? or Advice me to have more short code to Insert data?
Thanks in advance
You could redesign your classes to something like this
namespace timbangan
{
public static class Koneksi
{
public static MySqlConnection konek;
private static string konfigKoneksi = "Server=localhost;Database=timbangan;Uid=root;Pwd=";
public static MySqlConnection GetConnection()
{
konek = new MySqlConnection(konfigKoneksi);
konek.Open();
}
}//end of koneksi
public class Isidata
{
public int InsertData(string berat_filter, string qty, string nama_barang, string dari, string shift)
{
sql = #"insert into tb_timbang
(BERAT_FILTER,QTY,NAMA_BARANG,DARI,SHIFT)
values (#berat_filter,#qty,#nama_barang,#dari,#shift)";
try
{
using(MySqlConnection cnn = Koneksi.GetConnection())
using(MySqlCommand cmd = new MySqlCommand(sql, cnn))
{
cmd.Parameters.Add("#berat_filter", MySqlDbType.VarChar).Value = berat_filter;
cmd.Parameters.Add("#qty", MySqlDbType.VarChar).Value = qty;
cmd.Parameters.Add("#name_barang", MySqlDbType.VarChar).Value = nama_barang;
cmd.Parameters.Add("#dari", MySqlDbType.VarChar).Value = dari;
cmd.Parameters.Add("#shift", MySqlDbType.VarChar).Value = shift;
return cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show("error " + ex.Message);
return -1;
}
}
}
}//end of issdata
}//end of timbangan
In this design there are no more global variables around. The same Koneski class could be totally removed and your MySqlConnection could be created on the spot (reading the connectionstring from an external source like your config file). Don't think this is less efficient than keeping a global connection object already created and always open. There is an ADO.NET Connection Pooling infrastructure (link is for Sql Server but it is the same for MySql) that runs very efficiently to handle your connections
The important thing is the Using Statement (that closes and dispose the command and the connection when no more needed freeing valuable resources) and the parameters used to fill the command sent to the server. If you need to use an Adapter for other aspect of your work you could add other methods like this to your Isidata class
As a last note, notice that all parameters are of string type. This could work but it is best to have parameters of the same type of the field type on the database (and of course your variables should be of the correct datatype). This is particularly important with datetime fields that when are treated as strings could give a good headache to let them work correctly) See MySqlDbType enum
Make a class named DBClass.cs and write the below code-
class DBClass
{
MySqlCommand odcmd = new MySqlCommand();
MySqlConnection odcon = new MySqlConnection();
MySqlDataAdapter oda = new MySqlDataAdapter();
public DBClass()
{
}
public void OpenConnection()
{
odcon.ConnectionString = "Server=localhost;Database=timbangan;Uid=root;Pwd=";
if (odcon.State == ConnectionState.Closed)
odcon.Open();
oda.SelectCommand = odcmd;
odcmd.Connection = odcon;
}
public void CloseConnection()
{
if (odcon.State == ConnectionState.Open)
odcon.Close();
}
public DataTable Select(string sql)
{
DataTable dt = new DataTable();
odcmd.CommandText = sql;
oda.Fill(dt);
return dt;
}
public int ModiFy(string sql)
{
odcmd.CommandText = sql;
return odcmd.ExecuteNonQuery();
}
}
On your form, Now you can fire your query like-
DbclassObject.Modify(Your_Insert_Update_Delete_Query);
DataTable dt= DbclassObject.Select(Your_Select_Query);
I am trying to select a dynamic data from drop down list and view the details of selected items. I have no problem in populating the drop down list with data from database. However, when I selected some items, it does not shows up the detail.
In my presentation layer, when drop down list item is selected:
protected void ddlScheduleList_SelectedIndexChanged(object sender, EventArgs e)
{
string address = ddlScheduleList.SelectedItem.ToString();
Distribution scheduledIndv = new Distribution();
scheduledIndv = packBLL.getDistributionDetail(address);
if (scheduledIndv == null)
{
Console.Out.WriteLine("Null");
}
else
{
tbScheduleDate.Text = scheduledIndv.packingDate.ToString();
tbBeneficiary.Text = scheduledIndv.beneficiary;
}
}
In my business logic layer, I get the selected address and pass it to data access layer:
public Distribution getDistributionDetail(string address)
{
Distribution scheduledIndv = new Distribution();
return scheduledIndv.getDistributionDetail(address);
}
In my data access layer, I tested out the SQL statement already. It gives me what I wanted. But it just won't show up in web page.
public Distribution getDistributionDetail(string address)
{
Distribution distributionFound = null;
using (var connection = new SqlConnection(FoodBankDB.GetConnectionString())) // get your connection string from the other class here
{
SqlCommand command = new SqlCommand("SELECT d.packingDate, b.name FROM dbo.Distributions d " +
" INNER JOIN dbo.Beneficiaries b ON d.beneficiary = b.id " +
" WHERE b.addressLineOne = '" + address + "'", connection);
connection.Open();
using (var dr = command.ExecuteReader())
{
if (dr.Read())
{
DateTime packingDate = DateTime.Parse(dr["packingDate"].ToString());
string beneficiary = dr["beneficiary"].ToString();
distributionFound = new Distribution(packingDate, beneficiary);
}
}
}
return distributionFound;
}
And my execute Reader method in another separated class:
public static string connectionString = Properties.Settings.Default.connectionString;
public static string GetConnectionString()
{
return connectionString;
}
public static SqlDataReader executeReader(string query)
{
SqlDataReader result = null;
System.Diagnostics.Debug.WriteLine("FoodBankDB executeReader: " + query);
SqlConnection connection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
result = command.ExecuteReader();
connection.Close();
return result;
}
I wonder what went wrong. Is it about the (!IsPostBack) or?
Thanks in advance.
You found your mistake, but to avoid Sql Injection, modify your code like this:
SqlCommand command = new SqlCommand("SELECT d.packingDate, b.name FROM dbo.Distributions d " +
" INNER JOIN dbo.Beneficiaries b ON d.beneficiary = b.id " +
" WHERE b.addressLineOne = #address", connection);
command.Parameters.AddWithValue("#address", address);
when you select items from the listbox, you want to delete selecteditems. Why doesnt it work when selected data removed from database? I must have missed something. I got error message
No mapping exists from object type.
This is a method parameter:
IsDelete = _dinnerRemover.RemoveDinners(lstDinner.SelectedItems);
This class is to delete data from database
public bool RemoveDinners(dynamic dinnerItems)
{
Dinners = new List<FoodInformation>();
using (var sqlConn = new SqlConnection(_sqlConnectionString))
{
const string sqlQuery = "delete from DinnerTemplates where Dinner = #dinner";
using (var command = new SqlCommand(sqlQuery, sqlConn))
{
try
{
//command.CommandType = CommandType.StoredProcedure;
//command.CommandText = "sp_dinner";
foreach (var item in dinnerItems)
{
command.CommandType = CommandType.Text;
command.Parameters.AddWithValue("#dinner", item);
command.ExecuteNonQuery();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
sqlConn.Close();
}
}
}
return Dinners;
}
If dinnerItems is a list of strings then say that, don't use dynamic unless you absolutely have to.
To delete a bunch of items, issue one sql query with an IN clause. Don't issue lots of individual queries.
Try this:
public int RemoveDinners(List<string> dinnerItems)
{
using (var sqlConn = new SqlConnection(_sqlConnectionString))
{
const string sqlQuery = "delete from DinnerTemplates where Dinner in ({0})";
using (var command = new SqlCommand())
{
var paramNames = new string[dinnerItems.Count];
int i = 0;
foreach (string item in dinnerItems)
{
string paramName = "#Dinner" + i;
command.Parameters.AddWithValue(paramName, item);
paramNames[i] = paramName;
i += 1;
}
command.CommandText = String.Format(sqlQuery, String.Join(",", paramNames));
command.Connection = sqlConn;
command.CommandType = CommandType.Text;
sqlConn.Open();
return command.ExecuteNonQuery();
}
}
}
You have to bear in mind that you kind of left out some really relevant code, like what is a DinnerItem, since you're getting the error on a line related to its type.
However, the reason you're getting that error is because item can't be marshaled to a type of something like string or int.
That's probably because item is likely a custom class. One option would be to override the ToString method of the class:
public override string ToString() {
// return some property value, or set of property values
// strung together here.
}
another option would be to send in the actual Property you want off of item when issuing AddWithValue.
You need to define SqlDbType for command's parameter.
don't use dynamic type,use string..
if i were you,i would rather
IsDelete = _dinnerRemover.RemoveDinners(lstDinner.SelectedItems.ToString());
change the parameter to :
public bool RemoveDinners(string dinnerItems)
and the query to :
const string sqlQuery = "delete from DinnerTemplates where Dinner = dinnerItems";