DataKeyNames for different tables with different primary key names - c#

I'm wondering if there is a way to use datakeynames to access the primary keys of different tables if those primary key names are different in those tables.
For example, I have a table called Table1 and Table2 where the primary keys are Table1ID and Table2ID respectively.
My ultimate goal is to combine the contents of both tables into 1 gridview, and to generate a hyperlink for both every row. So my c# looks something like this:
protected void Page_Load(object sender, EventArgs e)
{
SqlCommand command = new SqlCommand("(SELECT Table1ID, Title FROM Table1" +
"UNION ALL SELECT Table2ID, Title FROM Table2)", connection);
GridView1.DataBind();
}
Then in my Gridview_Databound method, I go through and set the hyperlinks for each row individually. The problem is how do I access Table1ID and Table2ID? In my .aspx code, can I set that to be a BoundField because the DataField won't be consistent? I can't just do the following because then it neglects Table2ID right?
<asp:BoundField DataField="Table1ID"/>
Maybe I need to use a templateField or something?

You can use templatefield as below code
<asp:TemplateField HeaderText="Action">
<ItemTemplate>
<asp:LinkButton runat="server" ID="lnkEdit" ToolTip="Click to edit" CommandName="EditContent" CommandArgument='<%# Eval("Table1ID") %>' />
</ItemTemplate>
</asp:TemplateField>
Now on your OnRowCommand="GridView1_RowCommand" you will get the pk id of this tables.

In GridView control you can have a TemplateField containing Hyperlink (see the link to my article for details of this solution).
Also, it might be less labor-intensive if you use SqlDataSource assigning your select statement to its property and passing it to GridView.DataSource property. It will take just several lines of code and do the job for you. Regards, AB

Related

Assigning a cell value to NavigateUrl in GridView

GridView allows to create a virtual HyperLinkField column that converts another column assigned to it (and its cell values) into hyperlinks, but NavigateUrl must be assigned a "predetermined" url address.
However, in the GridView, the cells of the columns I created (Column_name) - are generated each time by running a function that creates a hyperlink address - as a relative address of the type
./(directory_with_different_name_each_time_when_the_value_is_generated)/index.aspx
Is it possible to make this content a "clickable" link to the newly generated subpage?
Any particular reason why say you don't just drop in a plane jane button, and use that?
create a virtual HyperLinkField column that converts another column assigned to it (and its cell values) into hyperlinks
Hum, that seems beyond confusing here??? If you asking can I create a hyper link for each row based on information from that data row that binds to the one row? Sure, no problem. You can use a messy Eval expression, but often it really OH SO MUCH easier to simple use the GV data bound event, and setup any kind of hyper link you want for that one button (or hyper link button).
I mean, we can have this gv:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID"
cssclass="table" Width="30%" OnRowDataBound="GridView1_RowDataBound">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" />
<asp:BoundField DataField="Animal" HeaderText="Animal" />
<asp:BoundField DataField="PictureName" HeaderText="Picture" />
<asp:TemplateField HeaderText="Example Hyperlink" ItemStyle-HorizontalAlign="Center" >
<ItemTemplate>
<asp:HyperLink ID="HyperLink1" runat="server">View</asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Example Button" ItemStyle-HorizontalAlign="Center" >
<ItemTemplate>
<asp:Button ID="Button1" runat="server" Text="View" CssClass="btn"
OnClick="Button1_Click"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
In above, for this demo, I dropped in a hyper link, and for good measure a plane jane button.
We will take the data FROM the data row.
I cannot beyond the mountains of Everst note how I grab the DATA source and NOT the grid view row. This allows me FULL USE of ANY and ALL columns for the data bound event. This means I do NOT even have to include the addtional columns in the grid, but ONLY IN THE DATA SOURCE!!
However, for this example, I have the columns in the gv.
So, with above, I did add a plane jane button with a button click even.
So, our code to fill the grid is this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
}
void LoadGrid()
{
using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT ID, Animal, PictureName from tblAnimals", conn))
{
conn.Open();
DataTable rstData = new DataTable();
rstData.Load(cmdSQL.ExecuteReader());
GridView1.DataSource = rstData;
GridView1.DataBind();
}
}
}
Output:
Ok, so lets cook up a hyper ink url for each hyper link button.
And lets ALSO do this for the button click.
So, in the gv row data bind event, we now have this:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// get data row
DataRowView gData = (DataRowView)e.Row.DataItem;
// example set hyper link
HyperLink myHyper = (HyperLink)e.Row.FindControl("HyperLink1");
// create URL based on row data
myHyper.NavigateUrl = "~/Content/Animals/" + gData["PictureName"];
// example buttion click
Button myBtn = (Button)e.Row.FindControl("Button1");
myBtn.CommandArgument = "~/Content/Animals/" + gData["PictureName"];
}
}
As you can see, we are free to cook up and create ANY kind of hyper link URL in code based on the columns in the data we feed to the grid.
As noted, I also tossed in a plane jane button - it has a click even, but NOTE close how I set the command argument for that button.
And thus the button click code does this:
protected void Button1_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
Response.Redirect(btn.CommandArgument);
}
So, both the hyper link, and the button click quite much wind up doing the same thing - navagating to a url of our choice for each row, and that url can be anything you wish to cook up in code based on the one data row.
Also, my code used SqlProvider, and you are likly using oleDB or ODBC provider, so just swap out the sqlcommand, Sqlconnection to the oleDB one, or ODBC one - the code will work much the same regardless of what provider you are using.

Value of HiddenField Duplicated when called from Nested Gridvew Row Editing

Problem: I'm trying to get a value from a HiddenField object and set it to a string. A value of "foo" is being returned as "foo, foo".
Details:
I have a Nested Gridview, and I'm trying to make the child Gridview editable. I have a method GetChildQuery(string id) which form the sql query to get the data for the Child Gridview, and I have the id bound to a HiddenField object in the HTML as so:
<asp:Gridview ID="gvChild" runat="server" AutoGenerateColumns="false" OnRowEditing = "gvChild_RowEditing">
<Columns>
<asp:TemplateField HeaderText="Name">
<ItemTemplate>
<asp:Label runat="server" ID="lblNameChild" Text='<% Eval("Name")%>'></asp:Label>
<asp:HiddenField runat="server" ID="hidIDChild" Value ='<%# Bind("ItemID") %>' />
</ItemTemplate>
<EditItemTemplate>
<asp:Label runat="server" ID="lblNameChildEdit" Text='<% Eval("Name")%>'></asp:Label>
<asp:HiddenField runat="server" ID="hidIDChildEdit" Value ='<%# Bind("ItemID") %>' />
</EditItemTemplate>
</asp:TemplateField>
and I try to access it in the OnRowEditing method like:
protected void gvhild_RowEditing(object sender, GridViewEditEventArgs e) {
Gridview child = sender as Gridview;
string itemID = (child.Rows[e.NewEditIndex].FindControl("hidIDChild") as HiddenField).Value.ToString();
string sql = GetChildQuery(itemID);
child.EditIndex = e.NewEditIndex;
child.DataSource = GetData(sql);
child.DataBind();
}
The id is used in the WHERE clause of my SQL query, and therefore the output is incorrect if the value of the id is incorrect. I don't know I this problem is occurring because of how I bind data to the HiddenField or how I'm calling it from the RowEditing method, or something I'm completely overlooking.
Ref: To make the Nested Gridview, I mainly followed https://www.aspsnippets.com/Articles/Nested-GridView-Example-in-ASPNet-using-C-and-VBNet.aspx and https://www.aspforums.net/Threads/133072/Edit-Update-Delete-in-Nested-Child-GridView-in-ASPNet/ which has the general format for my GetData method as well.
EDIT: Problem subverted by replacing the string itemID instantiation to
string itemID= ((child.Rows[e.NewEditIndex].FindControl("hidIDChild") as HiddenField).Value.Split(','))[0].ToString();
essentially splitting the duplication, accessing the first element, converting that to a string, and setting that equal to y string itemID.
However while this subverts the problem, I would still appreciate anyone's feedback as to why the problem is occurring in the first place. Thanks!
You should look at your rendered html.
When dealing with one gridview there is only ever one row open for editing at a time. And within a row's TemplateField the ItemTemplate and EditTemplate are mutually exclusive, that is to say that, when rendered, you will see only one or the other.
Likewise in the code behind, you will only have access to either the ItemTemplate row or the EditTemplate row depending on which event you process on postback.
The same rules apply to a nested/child Gridview.
So for the case you describe, I assume you are trying to use some ID from the parent to build a query to populate the child. If so I do not believe you need any hidden fields at all. This ID fieldname from the parents query should be set in the DataKeyNames property of the parent gridview.
You should then be able to pass the value to the procedure you are building for the child as
protected void gvhild_RowEditing(object sender, GridViewEditEventArgs e) {
string sql = GetChildQuery(gvParent.SelectedDataKey.Value);
child.DataSource = GetData(sql);
child.DataBind();
// Given that you have just populated the child I do not think the
// following is accurate, but that's for you to decide
child.SetEditRow(e.NewEditIndex);
}
Hope this helps.

How to get a column of checkboxes in Gridview?

One note is that I'm doing this all in Visual Studio 2013 Express for Web.
I have a GridView, and its data is being generated by a SqlDataSource. Currently, when I test the page. A table like below gets generated:
CustomerID CustomerName CustomerAddress
----------- ------------ ----------------
1 Bob Address
2 John Address
3 Smith Address
However, I really want this:
CustomerID CustomerName CustomerAddress
----------- ------------ ----------------
[] Bob Address
[] John Address
[] Smith Address
I want the CustomerID field to be "hidden field" and have a checkbox in its place. Then, I want the value of the checkboxes to be the CustomerID for that row. However, I can't for the life of me get the checkboxes in there, and it's still just displaying the CustomerID itself. In the end, I want to create a button that will delete the rows of whatever is checked, and have it reflected in the database via a DELETE FROM TABLE. This is why I want the checkboxes to also "have" a value of whatever the CustomerID is for that particular row.
Here's the code:
<asp:GridView ID="GridView1" runat="server" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="CustomerID" DataSourceID="RowsInGroup" >
<Columns>
<asp:BoundField DataField="CustomerID" HeaderText="CustomerID" ReadOnly="True" SortExpression="CustomerID" />
<asp:BoundField DataField="CustomerName" HeaderText="CustomerName" SortExpression="CustomerName" />
<asp:BoundField DataField="CustomerAddress" HeaderText="CustomerAddress" SortExpression="CustomerAddress" />
<asp:TemplateField></asp:TemplateField>
</Columns>
</asp:GridView>
If there's a better Data object to use in the Toolbox, I'm all ears.
Thanks for any advice you can provide!
You already have half of your problem solved. By using DataKeyNames="CustomerID", you have no need for a hidden field to hold this value.
First, create your check box column. There are multiple ways to accomplish this. Here is one:
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox ID="chkDelete" runat="server" />
</ItemTemplate>
</asp:TemplateField>
Then in whatever event handles the delete, just iterate each row in the GridView and find the checkbox in each row. If it is checked, use the DataKey for that row to get the CustomerID.
protected void btnDelete_Click(object sender, EventArgs e)
{
List<string> customersToDelete = new List<string>();
foreach(GridViewRow row in GridView1.Rows)
{
CheckBox chkDelete = (CheckBox)row.FindControl("chkDelete");
if(chkDelete.Checked)
{
DataKey key = GridView1.DataKeys[row.DataItemIndex];
customersToDelete.Add(key.Value.ToString());
}
}
}

Asp Gridview use Boundfields? or Templatefields?

I put data in a data table e.g.
dt.TableName = "SA1";
da.Fill(dt);
GridView1.DataSource = dt;
GridView1.DataBind();
Now I'm not sure if I should use boundfield (For all columns)
<asp:BoundField DataField="Unit" HeaderText="Unit" SortExpression="Unit" />
or use
<asp:TemplateField>
<HeaderTemplate>
Units
</HeaderTemplate>
<ItemTemplate>
<asp:TextBox runat="server" ID="txbUnits" Text='<%# Eval("Unit")%>'></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
and add the data as i go along, the gridview's purpose is only to display data
If you want to display rows without anything fancy, or any particular design, you would use BoundField. However if you would like to design the displaying of the record in a different manner than the default - you would need to create your own row template, by using the TemplateField.
Check out these links - they briefly explain the differences, but it is basically default VS customised presentation.
http://forums.asp.net/t/1369418.aspx?boundfield+vs+template+field+in+gridview
http://www.c-sharpcorner.com/Interviews/answer/1751/difference-between-boundfield-and-templatefield
If you want to just display the data then you should use bound field attribute.
The BoundField displays the value of specified DataSource field as
text.
The TemplateField allows for a mix of HTML markup, Web controls,
and data-binding syntax.
Your purpose is only to display data.So i think you need BoundField Here

GridViewRow FindControl query C#

I am building a simple C# web app which will act like an online database for resources.
In my table, I have a category and author column.
When I click a category value in the table, the table will refresh showing only the categories that was selected.
To do this I used the following code:
GridViewRow clickedRow = ((LinkButton)sender).NamingContainer as GridViewRow;
Label category = (Label)clickedRow.FindControl("lbl_category");
String selectedCategory = category.Text.ToString();
string query =
("SELECT * FROM main WHERE category='" + selectedCategory + "' ORDER BY ID ASC");
This works fine for the first time I click a category/author. But after the table has refreshed, and select another category or author then the table shows the wrong record.
How can I solve this?
You can view the page here: Try clicking at category 'Health' and then 'Puvent, Kevin'. The outcome is different to the one that was expected. I think
The question will probably make more sense once you see the page :)
EDIT - This is the gridview binding code:
<asp:TemplateField HeaderText="Category" ItemStyle-Width="15%">
<ItemTemplate>
<asp:Label ID="lbl_category" Text='<%# Bind("category")%>' runat="server" style="display:none;"></asp:Label>
<asp:LinkButton ID="lbl_linkCategory" Text='<%# Bind("category")%>' runat="server" OnClick="linkCategory" CommandArgument='<%# Bind("category")%>' />
</ItemTemplate>
</asp:TemplateField>
When you click on a category this changes the controls underneath. The control numbers changes. You reference that number and it is wrong, it needs to be constant and an ID of the table "main". You will have to find a way to assign the corresponding number to selectedCategory properly. You could do that by adding an attribute to the category-label, or check on what has been clicked. Like Finance => selectedCategory = 2, Health = 3, Family = 4 etc. Hopes it helps.
Using if (!IsPostBack) in Page_Load seemed to have solved the problem :)

Categories