For reference, I am new to C#/WPF/PostgreSQL and I am trying to create a project for practice, however I've hit a bit of a roadblock. I found this earlier and tried following along with the answers (I understand it isn't 1 to 1) with my own code: Retrieving data from database in WPF Desktop application but it didn't work in my case.
I am creating a simple recipe app where a user can create a recipe (e.g., put in the title, steps, things they need, etc.) and on the home screen, they can see a link to the recipe that was saved, which would take them to the Recipe Screen to be displayed if clicked. I am using PostgreSQL for my database and I do see the correct information on there after the user would submit all of the necessary info, I just need to retrieve it and put it in a data grid possibly? Unless there is a better way other than a data grid.
Regardless, I plan to have it shown as a list of just the title of the recipe, where a user can click on it and it would load up the page, but that's something I can tackle another time if that is outside of the scope in regards to my question.
Here is a visual idea of what I'm trying to accomplish:
Here is my code for the submit button found in the Create Screen if it helps, however I have no idea what to do in terms of actually retrieving that data and then displaying it on my Home Screen.
private static NpgsqlConnection GetConnection()
{
return new NpgsqlConnection(#"Server=localhost;Port=5432;User Id=postgres;Password=123;Database=RecipeProj;");
}
private void SubmitButton_Click(object sender, RoutedEventArgs e)
{
Recipe recipe = new Recipe();
recipe.Title = TitleBox.Text;
recipe.Step1 = StepBox1.Text;
recipe.Step2 = StepBox2.Text;
recipe.Step3 = StepBox3.Text;
recipe.Step4 = StepBox4.Text;
recipe.Step5 = StepBox5.Text;
recipe.Step6 = StepBox6.Text;
recipe.Ingredients = IngredientBox.Text;
recipe.Tools = ToolBox.Text;
recipe.Notes = NoteBox.Text;
void InsertRecord()
{
using (NpgsqlConnection con = GetConnection())
{
string query = #"insert into public.Recipes(Title, Ingredients, Tools, Notes, StepOne, StepTwo, StepThree, StepFour, StepFive, StepSix)
values(#Title, #Ingredients, #Tools, #Notes, #StepOne, #StepTwo, #StepThree, #StepFour, #StepFive, #StepSix)";
NpgsqlCommand cmd = new NpgsqlCommand(query, con);
cmd.Parameters.AddWithValue("#Title", recipe.Title);
cmd.Parameters.AddWithValue("#Ingredients", recipe.Ingredients);
cmd.Parameters.AddWithValue("#Tools", recipe.Tools);
cmd.Parameters.AddWithValue("#Notes", recipe.Notes);
cmd.Parameters.AddWithValue("#StepOne", recipe.Step1);
cmd.Parameters.AddWithValue("#StepTwo", recipe.Step2);
cmd.Parameters.AddWithValue("#StepThree", recipe.Step3);
cmd.Parameters.AddWithValue("#StepFour", recipe.Step4);
cmd.Parameters.AddWithValue("#StepFive", recipe.Step5);
cmd.Parameters.AddWithValue("#StepSix", recipe.Step6);
con.Open();
int n = cmd.ExecuteNonQuery();
if (n == 1)
{
MessageBox.Show("Record Inserted");
TitleBox.Text = IngredientBox.Text = ToolBox.Text = NoteBox.Text = StepBox1.Text = StepBox2.Text = StepBox3.Text = StepBox4.Text = StepBox5.Text = StepBox6.Text = null;
}
con.Close();
}
}
InsertRecord();
}
string query = #"select * from Recipes";
NpgsqlCommand cmd = new NpgsqlCommand(query, con);
con.Open();
var reader = cmd.ExecuteReader();
var recipes = new List<Recipe>();
while(reader.Read()){
//Recipe is just a POCO that represents an entire
//row inside your Recipes table.
var recipe = new Recipe(){
Title = reader.GetString(reader.GetOrdinal("Title")),
//So on and so forth.
//...
};
recipes.Add(recipe);
}
con.Close();
You can use this same exact query to fill in a List of titles and a DataGrid that shows all the contents of a recipe.
Related
I am new in coding and i was recently chosen to start training in C# and Asp.Net. I am trying to update an entry into the database but to no avail. The delete, create and read functions work properly. (i know that my code lacks try and catch)
I 've tried different solutions that i found while googling but since i am still training i never fully understand if what i am following is completely correct.
Currently i am able to diplay the fields with their info when navigating to the "Article" i am trying to edit but when i click the Save button nothing happens.
This is in my Article Context file:
public Article EditSingleArticle(int id, int userID, string Title, string Body)
{
Article article = new Article();
using (MySqlConnection conn = GetConnection())
{
MySqlCommand cmdslct = new MySqlCommand("SELECT * FROM Article WHERE ID=#id", conn);
MySqlCommand cmdul = new MySqlCommand("UPDATE Article SET userID=#userID,Title=#Title,Body=#Body WHERE ID=#id", conn);
conn.Open();
cmdslct.Parameters.AddWithValue("#ID", id);
cmdslct.ExecuteNonQuery();
cmdul.Parameters.AddWithValue("#userID", article.userID);
cmdul.Parameters.AddWithValue("#Title", article.Title);
cmdul.Parameters.AddWithValue("#Body", article.Body);
cmdul.ExecuteNonQuery();
using (MySqlDataReader reader = cmdslct.ExecuteReader())
{
while (reader.Read())
{
article = new Article()
{
userID = reader.GetInt32("userID"),
ID = reader.GetInt32("ID"),
Title = reader.GetString("Title"),
Body = reader.GetString("Body")
};
}
}
conn.Close();
}
The code below is in my ArticleController file:
public IActionResult Edit(int id, int userID, string Title, string Body)
{
ArticleContext context = HttpContext.RequestServices.GetService(typeof(ArticleContext)) as ArticleContext;
if (ModelState.IsValid)
{
return View(context.EditSingleArticle(id, userID, Title, Body));
}
return View();
}
I don't get any error messages but the article i am trying to edit never updates
The issue is that article object only have the default empty fields because you are calling cmdslct.ExecuteNonQuery() that does not return data at all, only the number of rows affected by an insert, update, or delete.
So you should call ExecuteReader() that returns an object that can iterate over the entire result set while only keeping one record in memory at a time.
Your code should looks like the following code in order to work as expected:
Article article = new Article();
using (MySqlConnection conn = GetConnection())
{
MySqlCommand cmdslct = new MySqlCommand("SELECT * FROM Article WHERE ID=#id", conn);
MySqlCommand cmdul = new MySqlCommand("UPDATE Article SET userID=#userID,Title=#Title,Body=#Body WHERE ID=#id", conn);
conn.Open();
cmdslct.Parameters.AddWithValue("#ID", id);
using (MySqlDataReader reader = cmdslct.ExecuteReader())
{
while (reader.Read())
{
article = new Article()
{
userID = reader.GetInt32("userID"),
ID = reader.GetInt32("ID"),
Title = reader.GetString("Title"),
Body = reader.GetString("Body")
};
}
}
cmdul.Parameters.AddWithValue("#userID", article.userID);
cmdul.Parameters.AddWithValue("#Title", article.Title);
cmdul.Parameters.AddWithValue("#Body", article.Body);
cmdul.ExecuteNonQuery();
conn.Close();
}
Regards,
In your update query you are using a where condition which uses parameter #id. But you are not passing the ID argument to your query. Add a argument '#id' for the update query.
Add this line to the code
cmul.Parameters.AddWithValue("#id", id);
May be a noob question. But I am scratching my head what I am doing wrong here.
protected void ddlClientNum_SelectedIndexChanged(object sender, EventArgs e)
{
DataTable dt = new DataTable();
using (SqlConnection con = new SqlConnection(Bussiness.GetConnectionString("Default")))
{
try
{
SqlDataAdapter adapter = new SqlDataAdapter("select distinct client_name from [dbo].[customer_master] where client_number=" + ddlClientNum.SelectedItem.Text + " order by client_name", con);
adapter.Fill(dt);
ddlClientName.DataSource = dt;
ddlClientName.DataTextField = "client_name";
ddlClientName.DataValueField = "client_name";
ddlClientName.DataBind();
ddlClientName.ClearSelection();
//ddlClientName.SelectedValue = ddlClientName.Items.FindByText((ddlClientNum.SelectedItem.Text).ToString()).Value;
//ddlClientName.SelectedValue = ddlClientName.Items.FindByText((dt.Rows[0][0]).ToString()).Value;
//ddlClientName.Items.FindByText((dt.Rows[0][0]).ToString()).Selected = true;
ddlClientName.SelectedIndex = ddlClientName.Items.IndexOf(ddlClientName.Items.FindByText((dt.Rows[0][0]).ToString()));
}
}
}
I am populating client number and client name dropdownlist on page load. On selection of client number I need to select the respective client name but I don't want to Clear the Items. I need users to be able to see the other client names but need to Select the Client name for selected client Number. I Have tried the 3 things but it keeps adding the selected client name.
So, selected client name is occurring as many times I select the respective Client Number and stays there even if I select another client Number.
e.g. I select Client Number :176 | Client Name shows "XYZ Client" selected but occurs twice in the list. If I select any other client number and then again select 176 I can see "XYZ Client" occuring in the list thrice.
Blues and yellows are the repeating ones.
Then you don't need to bind again here. just set the selected index.
Also, I suggest you to use Parameterized queries. Otherwise, It will be vulnerable to SQL injection.
For more info : https://www.mssqltips.com/sqlservertip/2981/using-parameters-for-sql-server-queries-and-stored-procedures/
protected void ddlClientNum_SelectedIndexChanged(object sender, EventArgs e)
{
DataTable dt = new DataTable();
using (SqlConnection con = new SqlConnection(Bussiness.GetConnectionString("Default")))
{
try
{
SqlDataAdapter adapter = new SqlDataAdapter("select distinct client_name from [dbo].[customer_master] where client_number=" + ddlClientNum.SelectedItem.Text + " order by client_name", con);
adapter.Fill(dt);
ddlClientName.ClearSelection();
ddlClientName.SelectedIndex = ddlClientName.Items.IndexOf(ddlClientName.Items.FindByText((dt.Rows[0][0]).ToString()));
}
}
}
I am currently setting the tooltips on a report grid based on values stored in a table. I do this because I have a LOT of grids and a lot of tooltips and this makes it easy to manage them all from one place without updating source code.
My question. Is it faster to load the tooltips in this fashion or to load them by loading all of tooltips at once and looping through an array?
It seems that one SP call for all of the tooltips would be faster than 10-20. Is this assumption correct? If so, can I see an example of how you'd do this in an array or list?
sqlconn.Open();
SqlCommand com = new SqlCommand("sp_ToolTipLookup", sqlconn) { CommandType = System.Data.CommandType.StoredProcedure };
SqlParameter pFieldName = new SqlParameter("#FieldName", "");
for (int i = 0; i < rptgrid.Columns.Count; i++)
{
pFieldName.Value = rptgrid.Columns[i].ToString();
com.Parameters.Add(pFieldName); //adding the field name to the SP
SqlDataReader data = com.ExecuteReader(); //Open the SP
if (data.Read()) rptgrid.Columns[i].ToolTip = data["ToolTip"].ToString(); //If there is a resulting Tooltip, apply it to the grid
data.Close();
com.Parameters.Remove(pFieldName);
}
sqlconn.Close();
An example using a list would be more like this (and if this is faster, I could potentially load the list once per session and just store it in memory).
sqlconn.Open();
SqlCommand com = new SqlCommand("Select * from ToolTips", sqlconn) { CommandType = System.Data.CommandType.Text };
SqlDataReader data = com.ExecuteReader();
List<ToolTip> tips = new List<ToolTip>();
while (data.Read())
{
tips.Add(new ToolTip { fieldname = data["FieldName"].ToString(), tooltip = data["ToolTip"].ToString() } );
}
for (int i = 0; i < rptgrid.Columns.Count; i++) //Changed to visible column to speed it up a bit.
{
for (int x = 0; x < tips.Count; x++)
{
if (rptgrid.Columns[i].Name == tips[x].fieldname)
{
rptgrid.Columns[i].ToolTip = tips[x].tooltip;
}
}
}
data.Close();
sqlconn.Close();
The stored proc sp_ToolTipLookup must return at least the data ToolTip and FieldName, but you have to remove the filter about the tool tip name in the where clause..
string connectionString = ... //web|app.config
using (SqlConnection sqlconn = new SqlConnection(connectionString)){
using(SqlCommand com = new SqlCommand("sp_ToolTipLookup", sqlconn)){
com.CommandType = System.Data.CommandType.StoredProcedure
sqlconn.Open();
using (SqlDataReader data = com.ExecuteReader()){ //Call the SP
while(data.Read()) {
foreach(var col in rptgrid.VisibleColumns){
if (col.Name == data["FieldName"].ToString()){
rptgrid.VisibleColumns[col.Index].ToolTip = data["ToolTip"].ToString();
}
}
}
}
}
}
Correct one SP call loading all tooltips would be faster provided that the SP is designed efficiently.
Since the Tooltips are likely not going to change while the application is running, I would recommend actually loading your tooltips into your application as a public static property of your Main or Program or whatever your root class is for your app. This would make tooltips available to the entire application and avoid different parts of the apps having to make different database calls to get their tooltips. I'd also put a time checker in the property Get method so that every few hours the data is refreshed.
Ok so to summarize:
The answer is a combination of three received so far.
One SP to load all ToolTips
Do this once and make them available to the application.
Thanks guys. I wish I could select more than one correct answer.
I'm currently creating a small application using Windows Forms and SQLite. After reading some tutorials I implemented this method for data retrieval:
public DataTable GetDataTable(ref SQLiteDataAdapter adapter, string sql)
{
DataTable dt = new DataTable();
// Connect to database.
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
// Create database adapter using specified query
using (adapter = new SQLiteDataAdapter(sql, connection))
// Create command builder to generate SQL update, insert and delete commands
using (SQLiteCommandBuilder command = new SQLiteCommandBuilder(adapter))
{
// Populate datatable to return, using the database adapter
adapter.Fill(dt);
}
return dt;
}
(As well as another GetDataTable which doesn't take an SQLiteDataAdapter as parameter)
I have three classes, let's call them UI, Link and Database. The UI does nothing but displaying the data and raising events upon user interaction. The Link creates the Database and a SQLiteDataAdapter, retrieves a data table through the method mentioned above, and binds it to a data grid view on the UI. The user cannot alter the table through the data grid view, but should do so through some text boxes. (does this make binding the table to the dgv obosolete?)
What's the best way to get the user input from the text boxes to the database, using the adapter? Or should I use DataReader and some Insert method instead of an adapter?
As of know, the UI exposes its controls through Get-methods. Is there a better solution?
private void Initialize()
{
// Subscribe to userInterface events
userInterface.DataGridViewSelectionChanged += new EventHandler(userInterface_DataGridViewSelectionChanged);
userInterface.NewClicked += new EventHandler(userInterface_NewClicked);
userInterface.SaveClicked += new EventHandler(userInterface_SaveClicked);
// Get dataGridView from userInterface and bind to database
bindingSource = new BindingSource();
bindingSource.DataSource = database.GetDataTable(ref adapter, "SELECT * FROM SomeTable");
userInterface.GetDataGridView().DataSource = bindingSource;
}
void userInterface_DataGridViewSelectionChanged(object sender, EventArgs e)
{
if (userInterface.GetDataGridView().SelectedRows.Count != 0)
{
DataGridViewRow row = userInterface.GetDataGridView().SelectedRows[0];
userInterface.GetIDTextBox().Text = row.Cells["PrimaryKey].Value.ToString();
userInterface.GetOtherIDTextBox().Text = row.Cells["ForeignKey"].Value.ToString();
DataTable dt = database.GetDataTable("SELECT * from SomeTable WHERE ForeignKey=" + row.Cells["ForeignKey"].Value);
userInterface.GetLastNameTextBox().Text = dt.Rows[0]["LastName"].ToString();
userInterface.GetFirstNameTextBox().Text = dt.Rows[0]["FirstName"].ToString();
userInterface.GetCompanyTextBox().Text = dt.Rows[0]["Company"].ToString();
}
}
void userInterface_NewClicked(object sender, EventArgs e)
{
// Get all text boxes and clear them
// Let the UI take care of this by itself?
}
void userInterface_SaveClicked(object sender, EventArgs e)
{
// Get text/data from all text boxes and insert (or update if editing table) into database
// adapter.Update(...)?
}
Cheers!
INSERT, UPDATE and DELETE operations are the working of a DbCommand. You need a different method that takes the sql string and a collection of SQLiteParameter that you use for the INSERT.
I will try to write some pseudocode for the INSERT operation
public class MyHelperClass
{
public static int InsertCommand(string sql, SQLiteParameter[] parameters)
{
int result = 0;
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
using (SQLiteCommand cmd = new SQLiteCommand(sql, connection))
{
cmd.Parameters.AddRange(parameters);
result = cmd.ExecuteNonQuery();
}
return result;
}
}
Now you have to build the parameter array to pass to the help method and this should be done from your UI code
string sqlCommand = "INSERT INTO table1 (FirstName, LastName) VALUES (#fName, #lName)";
SQLiteParameter[] p = new SQLiteParameter[2];
p[0] = new SQLiteParameter("#fName", TextBox1.Text);
p[1] = new SQLiteParameter("#lName", TextBox2.Text);
int rowAdded = MyHelperClass,InsertCommand(sql, p);
The operation for the UPDATE and DELETE command are similar. Also I suggest you to add a version of your GetDataTable that accepts a parameter array instead of building sql commands with string concatenation. As repetead innumerable times here string concatenation leads to errors and, worst of all, to weak code easily exposed to sql injection.
Sorry in advance im going to try and explain this as best as possible....
I have 2 asp.net pages one named membermaster and the second named memberdetails. I created a class library which contains 2 functions
My first function returns a list depending on the search result...
I added a linkbutton to the gridviews first column which when clicked it passes through querystring the membershipgen. What i wanted to do is for my second function i created this
public DataTable GetMembers(int MEMBERSHIPGEN)
{
DataTable table = null;
SqlConnection con = null;
SqlCommand cmd = null;
SqlDataAdapter ad = null;
SqlParameter prm = null;
try
{
table = new DataTable();
using (con = new SqlConnection(connectionString))
{
using (cmd = new SqlCommand("usp_getmemberdetail", con))
{
using (ad = new SqlDataAdapter(cmd))
{
prm = new SqlParameter("#MEMBERSHIPGEN", SqlDbType.Int);
prm.Value = MEMBERSHIPGEN;
cmd.Parameters.Add(prm);
ad.Fill(table);
}
}
}
}
catch (Exception ex)
{
//write your exception code here
}
return table;
}
In the attempt to try and send the membershipgen to this and it return the results. But once i compile the DLL and add it to my project I am not sure how i would reference this function to populate individual textboxes and labels with the information.
What I am trying to do is when a user clicks the viewdetails button on the gridview I can then use that membershipgen that I passed through querystring to populate the page through a stored procedure but the smarts would be stored in a DLL.
You probably want your method to return a value. Currently the return type is void, so the values it populates internally just go away when the call stack leaves the method. It sounds like you want something like this:
public DataTable GetMembers(int MEMBERSHIPGEN)
Then, in your method, after you've populated the DataTable and exited the using blocks, you'd do something like this:
return table;
This would return the DataTable to whatever called the method. So your page would have something like this:
DataTable table = GetMembers(membershipgen);
So the page would be responsible for:
Get the membershipgen value from the input (query string)
Call the method and get the result of the method
Display the result from the method (bind to a grid? or whatever you're doing to display the data)
And the method is responsible for:
Interact with the database
This is a good first step toward the overall goal of "separation of concerns" which is a very good thing to do. You can continue down this path by always asking yourself what each method, class, etc. should be responsible for. For example, your GetMembers method should also be responsible for ensuring that the value passed to it is valid, or that the value returned from it is not null.
You need to change GetMembers to return data instead of void. If you want to use DataTables, you can just modify your code to this:
public DataTable GetMembers(int MEMBERSHIPGEN)
{
DataTable table = new DataTable();
SqlConnection con = new SqlConnection(connectionString);
using (SqlCommand cmd = new SqlCommand("usp_getmemberdetail", con))
{
using (SqlDataAdapter ad = new SqlDataAdapter(cmd))
{
SqlParameter prm = new SqlParameter("#MEMBERSHIPGEN", SqlDbType.Int);
prm.Value = MEMBERSHIPGEN;
cmd.Parameters.Add(prm);
ad.Fill(table);
return table;
}
Then in your Page_Load it might be something like this (more robust than this hopefully):
{
DataTable table = yourDll.GetMembers(Convert.ToInt32(Request.QueryString["membership"]));
label1.Text = Convert.ToString(table.rows[0]["Name"]);
}
One way to go might be to construct the button so that it navigates to a url along the lines of:
http://localhost/DetailPage.aspx?membershipgen=4
Then in the load of the DetailPage.aspx:
Page_Load(Object sender, EventArgs e)
{
if (!this.IsPostback)
{
int membershipgen;
if (int.TryParse(Request.QueryString["membershipgen"], out membershipgen)
{
//Get the data (replace DataAccess with the name of your data access class).
//Also, you probably want to change GetMembers so it returns the data.
DataTable table = DataAccess.GetMembers(membershipgen);
//TODO: Display the results
}
}
else
{
//Display an error
}
}