Autocomplete textbox in layered architecture - c#

I want to create an autocomplete textbox with my database.
I'm programming my application in a layered architecture (models, DAL, BLL, Presentation).
I've already made a method with an arraylist that reads and returns my select command in the database, which is filling (I've tested on a combobox).
But when I try to insert in the the textbox, nothing happens... it doesn't show the suggestion.
I looked for something in the forum but I just found examples with one layer and, since I'm developing in layers I cannot increment the property AutoCompleteStringCollection in my DAL to be filled by my select command.
If anyone has any idea how to solve this problem, please explain to me!
Additional information: I'm using winForm with C# and SQL Server.

I think you want to say that "But when i try to insert in the textbox, nothing happens... it doesn't show the sugestion."
well i cannot just code all layers here but can suggest in your DAL create a method which returns List and then on your form page provide code like this
txtName.AutoCompleteMode = AutoCompleteMode.Suggest;
txtName.AutoCompleteSource = AutoCompleteSource.CustomSource;
var autoCompleteCollection = new AutoCompleteStringCollection();
autoCompleteCollection.AddRange(DAL.GetMethod().ToArray());
textbox.AutoCompleteCustomSource = autoCompleteCollection;

Thanks for the help!!
I used your suggest and had made some little changes and it work just fine to me...
Turns out that the only problem was my method list, once I change it do a List < String > things got better.
For who is wondering, here is how I do it:
DAL LAYER:
public List<string> LoadList()
{
List<string> tagsList = new List<string>();
using (SqlConnection connection = new SqlConnection(ADados.StringDeConexao))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT column FROM table";
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
if (!reader.IsDBNull(0))
tagsList.Add(reader.GetString(0));
}
reader.Close();
}
connection.Close();
return tagsList;
}
PRESENTATION LAYER (Event TextChanged):
PedidoBLL pedido = new PedidoBLL();
txtName.AutoCompleteMode = AutoCompleteMode.Suggest;
txtName.AutoCompleteSource = AutoCompleteSource.CustomSource;
AutoCompleteStringCollection popula = new AutoCompleteStringCollection();
popula.AddRange(pedido.LoadList().ToArray());
txtName.AutoCompleteCustomSource = popula;
In the BLL Layer i just call and return the DAL method LoadList...

Related

How can I retrieve data from my PostgreSQL database? WPF/C#

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.

Multiple stored procedure calls or loop through an array?

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.

Add combo box items from data layer c#

I have a data layer class where I create all the SQL statements inside methods. In my UI layer i have a combo box. What I want to do is, I want to fill the combo box from the data layer, not from the UI layer.
So far I have typed this code inside data layer....
public void ComboImageList()
{
try
{
SqlCommand command = new SqlCommand("SELECT ImageName FROM [ImageWithTags]", con);
con.Open();
SqlDataReader reader = command.ExecuteReader();
while(reader.Read())
{
string name = reader.GetString(reader.GetOrdinal("ImageName"));
}
con.Close();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Since I cannot access UI layer directly from Data layer, I dont know how to pass the values. I dont know if I can pass the combo box to the data layer or I have to pass the string to the UI layer.
can someone help?
You shouldn't do that, because such design leads to numerous problems: awkward testing, hard to refactor, relying on particular database and UI controls where you shouldn't, it will be hard to reuse DAL from other places.
Diagram
User -> UI -> DAL -> Database
|
User <- UI <- DAL <------
Proper way of doing this will be to have a method in DAL like
IEnumerable<string> GetNames()
{
// execute your SQL and return result as some abstract collection
using( /* connection setup */
{
using(var command = new SqlCommand("SELECT ImageName FROM [ImageWithTags]", con))
{
con.Open(); // check this, maybe it could be opened in first using
SqlDataReader reader = command.ExecuteReader();
while(reader.Read())
{
yield return reader.GetString(reader.GetOrdinal("ImageName"));
}
}
}
}
And in UI layer you just call it like so
public void FillCombos()
{
var repo = new DALRepo();
var names = repo.GetNames();
// now you can assign names to your combobox
// ...
}

Proper way of getting a data from an Access Database

I'm a bit confused of how to get a data from an access database. Is it proper to gather it first in a List then get those data from your List OR it is okay to just directly get it in you database ?
My codes work perfectly fine, but I wanna know if there is a better way to do this?? :
private void button3_Click(object sender, EventArgs e)
{
OleDbConnection connection = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\redgabanan\Desktop\Gabanan_Red_dbaseCon\Red_Database.accdb");
connection.Open();
OleDbDataReader reader = null;
OleDbCommand command = new OleDbCommand("SELECT * from Users WHERE LastName='"+textBox8.Text+"'", connection);
reader = command.ExecuteReader();
listBox1.Items.Clear();
while (reader.Read())
{
listBox1.Items.Add(reader[1].ToString()+","+reader[2].ToString());
}
connection.Close();
*I'm getting my records directly from a database then display it in a listbox.
One thing that is sticking out like a sore thumb is the SQLInjection and to use Parameterised queries, eg:
OleDbCommand command = new OleDbCommand("SELECT * from Users WHERE LastName='#1'", connection);
command.Parameters.AddWithValue("#1", textBox8.Text)
What your doing is perfectly acceptable, although you would generally be better off to use a SQL Database.
Edit:
Here is how you seperate your business logic from the GUI:
Class BusLogic
{
public List<string> ListboxItems = new List<string>();
public void PopulateListBoxItems(string userName)
{
string connString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\redgabanan\Desktop\Gabanan_Red_dbaseCon\Red_Database.accdb";
using (OleDbConnection connection = new OleDbConnection(connString))
{
connection.Open();
OleDbDataReader reader = null;
OleDbCommand command = new OleDbCommand("SELECT * from Users WHERE LastName='#1'", connection);
command.Parameters.AddWithValue("#1", userName)
reader = command.ExecuteReader();
while (reader.Read())
{
ListboxItems.Add(reader[1].ToString()+","+reader[2].ToString());
}
}
}
}
GUI
private void button3_Click(object sender, EventArgs e)
{
var busLogic = new BusLogic();
busLogic.PopulateListBoxItems(textBox8.Text);
\\listBox1.Items.Clear();
ListboxItems.DataSource = busLogic.ListboxItems;
}
The beauty of this "MVC" approach is that we only really need to test the BusLogic if we rely on controls being bound using Binding.
ps Ideally ListboxItems would be an IEnumerable instead of List so that we don't expose any functionality to Add/Remove etc from the caller. This is good API design.
I would say the answer is "yes" to both.
What you're doing now is perfectly acceptable for simple cases. Just be aware that it doesn't "scale" very well. That is, loading 10 or 20 items is fine. But what happens if it becomes 10 thousand or a million?
In that case you want to look at using a Model-View-Controller (MVC) architecture. That's a topic in itself, but basically you decouple the listbox (the "view") from the data (the "model").
See this site for a C#-centric MVC discussion
In between what you're doing now and a full-blown MVC architecture, you may simply want to do as you suggest - load the list first then add them to the list box. That gains you nothing if you just load it once, but if the list is loaded "all over the place", you can save the database IO overhead each time by just accessing it once.
The fact that you thought to ask the question indicates you're on the right track.
Although your code works without any problem, I suggest you to perform some exception handling as in this example, since both OleDbConnection.Open() and OleDbCommand.ExecuteReader() might throw an InvalidOperationException.
It is also common to wrap the connection with a using statement, so in the end connection.close() is called automatically, but this is just a personal preference.
You can maybe separate your data access functions in different classes or create generic functions to retrieve records.

C# passing information

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
}
}

Categories

Resources