How can I issue an additional select from within a repeater - asp.net

I am looking for a line of code that can show how many replies a topic has in my forum. I have a repeater(REPEATER_1) to show other info about each topic, like it's title and text. For example, to get the title, I use:
<%# DataBinder.Eval(Container, "DataItem.title")%>
This works fine, but for counting replies, I need to access another table, and count all replies for this topic. I use the following SelectCommand:
"SELECT COUNT(ID) FROM [replies] WHERE parent='" & <%# DataBinder.Eval(Container, "DataItem.ID")%> & "';"
But how do I execute this SelectCommand from within the Form (and within the repeater area) of the page using <%# XXXX.. %>
I know there are alternatives using code-behind, but I am practicing doing it this way using <%# XXXX.. %>
Also, what is it called when doing script inside a form using "<%# XXXX.. %>" ? It will make it easier for me to search on the web, as google or this website cannot search for "<%#"

I would do this in the ItemDataBound event of the Repeater.
I'm following your current approach, but you should return the reply count with the query that gets the topics. Doing it the way you're doing it, you're calling the database too many times.
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// !! use a parameterized query here to avoid SQL injection
string query = String.Format("SELECT x FROM y WHERE z = {0}", DataBinder.Eval(e.Item.DataItem, "SomeColumn"));
//execute your query to get reply count
int replyCount = ExecuteQuery(query); // !! example
Label lbl = e.Item.FindControl("ReplyCountLabel") as Label;
if (lbl != null)
{
lbl.Text = replyCount.ToString()
}
}

Related

Would using TOP (N) and OPTION(FAST N) interfere with GridView's built-in Optimizations?

I am using a GridView which could potentially display half a million records (depending on the options the user selects in filtering the data).
I want to prevent this, for optimization/performance purposes. If it would dramatically improve performance, I am willing to limit the result set to a few thousand or so records.
Would starting my query with a TOP (N) and ending with a OPTION(FAST N) be a good thing for this situation, or would it interfere with GridView's built-in optimization (I'm assuming, and I think I read it, that GridView only loads so many records at a time, as the user scrolls through).
So should I do something like this:
SELECT TOP (1000) [column list]
FROM [table]
WHERE [bla]
ORDER BY [Bla]
OPTION(FAST 100)
...or am I better off leaving it at
SELECT [column list]
FROM [table]
WHERE [bla]
ORDER BY [Bla]
...due to a possibility that my attempts at optimization will interfere with what the GridView is already handling on its own?
The GridView loads all rows. As far as I know it does not optimize anything. And if you do not disable ViewState for the GridView (or the page it is on) all those rows are added to the ViewState, resulting in a HUGE page size of way too much MB's.
The paging in the GridView is nothing more than just showing the rows 100 - 200, while the rest still is loaded.
So I would do something like this (more info). Note that order by is required in this case.
SELECT [column list]
FROM [table]
WHERE [bla]
ORDER BY [Bla]
OFFSET [Offset] ROWS FETCH NEXT [Pagesize] ROWS ONLY
Now, how to implement this for a GridView. First, create a Stored Procedure. You could do it without one, but because you also would need the total number of rows you would need two requests to the database. In this example the data is required by a userID as the WHERE parameter.
CREATE PROCEDURE [dbo].[GridView_Paging]
#offset int,
#pagesize int,
#userID int
AS
BEGIN
DECLARE #totalrows INT;
-- you will need the total amount of rows for paging to work
SELECT #totalrows = COUNT(itemID) FROM MyTable WHERE userid = #userID;
--select the data and add the total rows also
SELECT ID, name, #totalrows AS totalrows
FROM MyTable
WHERE userID = #userID
ORDER BY ID
OFFSET #offset ROWS FETCH NEXT #pagesize ROWS ONLY;
END
GO
Then add a GridView to the aspx page and make sure ViewState is off (this saves network traffic). And add a Repeater that will function as the Pager.
<asp:GridView ID="GridView1" runat="server" EnableViewState="false"></asp:GridView>
<asp:Repeater ID="Pager_GridView" runat="server">
<ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server" OnCommand="Pager_GridView_Command"
CommandArgument='<%# Container.ItemIndex %>'><%# Container.ItemIndex + 1 %>
</asp:LinkButton>
</ItemTemplate>
</asp:Repeater>
Then a function to call the stored procedure and add the data to the GridView and create the pager. I add the data to a DataTable first instead of directly to the GridView1.DataSource because I need the total number of rows.
public void LoadGridView(int page, int userID)
{
int pagesize = 10;
double totalrows = 0;
int offset = page * pagesize;
//create a connection
using (var connection = new SqlConnection(Common.connectionString))
using (var command = new SqlCommand("GridView_Paging #offset, #pagesize, #userID", connection))
{
//add the parameters
command.Parameters.Add("#offset", SqlDbType.Int).Value = offset;
command.Parameters.Add("#pagesize", SqlDbType.Int).Value = pagesize;
command.Parameters.Add("#userID", SqlDbType.Int).Value = userID;
//open the connection
connection.Open();
using (var reader = command.ExecuteReader())
{
var dt = new DataTable();
dt.Load(reader);
//add the data to the gridview
GridView1.DataSource = dt;
GridView1.DataBind();
//get the total rows from the datatable
if (dt.Rows.Count > 0)
totalrows = (int)dt.Rows[0]["totalrows"];
}
}
//calculate the number of pages
var pages = Math.Ceiling(totalrows / pagesize);
//create the pager by binding an array of the correct size to the repeater
Pager_GridView.DataSource = new string[(int)pages];
Pager_GridView.DataBind();
}
Then add the Repeater LinkButton command
protected void Pager_GridView_Command(object sender, CommandEventArgs e)
{
LoadGridView(Convert.ToInt32(e.CommandArgument), 12345);
}
And finally Page_Load. Because I've disabled the ViewState, the data has to be bound on every page load and PostBack (which is also the same page load basically). If you would just put LoadGridView(0, 12345); then you would be double-hitting the database. Once in Page_Load and once you click a link in the pager. To prevent this, check if the __EVENTTARGET is from the Repeater
protected void Page_Load(object sender, EventArgs e)
{
string eventTarget = Request.Form["__EVENTTARGET"];
//check the eventtarget contains the repeater container to prevent double hitting
if (string.IsNullOrEmpty(eventTarget) || !eventTarget.Contains(Pager_GridView.UniqueID))
{
LoadGridView(0, 12345);
}
}
this way is good but use withe below way
first add a object datasource to page and set connecting for this datasource and write below code and this code must write click button event
datasource1.SelectCommand = "SELECT TOP ("+txtuserfilter+") [column list] FROM [table] WHERE [bla] ORDER BY [Bla] OPTION(FAST 100)";
GridView1.DataSourceID = "datasource1";

Show data in html from database

I have a select query which is return just one row data. I want to show them some different part of a page, not in a repeater, datalist etc...
I do not use asp:label or somtehing like that, then how can I show them from client side like eval or how can I bind Codebehind and call them from client side in html?
edit:
codebehind:
DataTable dt = new DataTable(); ;
dt = myprocedur.User_Load(Int32.Parse(Session["User_ID"].ToString())).Tables[0];
That I want to bind it something or somewhere in page Load.
Then in HTML:
<div><%#Eval("User_Name") %><div>
Use like that where I want, whithout <asp:blabla runat:server />, what is the way of that?
If is it possible, can you give me some exapmle?
edit2:
c#
public static string getData()
{
return "abcd";
}
html
<div><%getData%></div>
I guess we can use st like that, can we adapt it for data from database or similar way for it?
Use Form View ,
<asp:FormView ID="FormView1" runat="server"
DataSourceID="yourDataSource">
<ItemTemplate>
<%# Eval("User_Name") %>
</ItemTemplate>
</asp:FormView>
You can reference here !
In my example , I use DataSourceID="yourDataSource" , if you want to bind fromView's data source from code behind , you can bind likes
FormView1.DataSource = yourDataSoruse ;
FormView1.DataBind();
Edit
DataTable dt = new DataTable(); ;
dt = myprocedur.User_Load(Int32.Parse(Session["User_ID"].ToString())).Tables[0];
FormView1.DataSource = dt ;
FormView1.DataBind();

How to get textbox value in ASP.NET click eventhandler

Somehow one of my stored procedures just stopped executing from aspx page. It works if I run it from SQL Server using EXEC. But when I click a button on my aspx page which assigns parameters and values, and launches this procedure, page reloads but data is not updated. This button can run create or update procedure, depending on the page parameters in the address bar. But nothing is executed.
In the aspx page I create that button like this:
<asp:Button id="btnSaveChanges" runat="server"
class="class_Button Normal Enabled"
OnClick="btnSaveChanges_Click" Text="Save changes" Width="100" />
Then in the code-behind file:
protected void btnSaveChanges_Click(object s, EventArgs e)
{
//if (Page.IsValid)
//{
SqlCommand sqlComm1 = new SqlCommand();
SqlConnection sqlConn1 = new SqlConnection("Server=localhost\\SqlExpress;Database=TestDB;Integrated Security=true");
sqlConn1.Open();
if(param_id == 0)
{
sqlComm1 = new SqlCommand("dcspCreateEmpDetails", sqlConn1);
}
if(param_id > 0)
{
sqlComm1 = new SqlCommand("dcspUpdateEmpDetails", sqlConn1);
}
sqlComm1.CommandType = CommandType.StoredProcedure;
if (param_id > 0)
sqlComm1.Parameters.AddWithValue("#empID", param_id);
sqlComm1.Parameters.AddWithValue("#empName1", tbName1.Text);
sqlComm1.Parameters.AddWithValue("#empSurname", tbSurname.Text);
sqlComm1.Parameters.AddWithValue("#empBirthDate", Convert.ToDateTime(tbBirthDate.Text));
sqlComm1.ExecuteNonQuery();
//}
sqlConn1.Close();
}
That's it. Page is valid, 100%. And even if I remove validation check, no result.
Here is the procedure:
CREATE PROCEDURE dcspUpdateEmpDetails
#empID int,
#empName1 nvarchar(50) = null,
#empSurname nvarchar(50) = null,
#empBirthDate datetime = null
AS
UPDATE Employees
SET
name_1 = #empName1,
surname = #empSurname,
date_of_birth = #empBirthDate
WHERE (employee_id = #empID)
Hope you'll help me with it, guys. I really don't understand what happened to this stuff...
Updates for the topic:
Examining debug messages I found, that textbox loses its text before the stored procedure in the OnClick event takes this text as a parameter.
Is it really normal that server first restores the page and only after that it executes code in OnClick event? I think it's logically incorrect because Page_Load is designed to load the default page, while buttons and other controls are used to change and manipulate content of a page. Why do I need those controls if their code can't execute timely?
I took my backup copy and copied all stuff regarding this problem to my current version. It was just the same absolutely identical code. I just replaced current code with the same code from backup and it works now. What was that?
I can't mark anything as an answer because there are only comments here. I'll mark this one.

Populate asp labels from SQL Query

I wrote the code to query the database, but now do not know how to get the text into my two labels 'txtTitle' & 'txtBody'
protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
{
SqlConnection thisConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["blcDocumentationConnectionString"].ConnectionString);
// Create Command Object
SqlCommand nonqueryCommand = thisConnection.CreateCommand();
pnlNew.Visible = false;
pnlView.Visible = true;
try
{
// Open Connection
thisConnection.Open();
// Create SELECT statement with named parms
nonqueryCommand.CommandText = "SELECT DocumentTitle,DocumentBody FROM tblDocument WHERE DocumentID = #DocumentID";
// Add parms to Command parms collection
nonqueryCommand.Parameters.AddWithValue("#DocumentID", GridView1.SelectedValue);
// Execute query statement
nonqueryCommand.ExecuteNonQuery();
// Populate Labels
GridViewRow row = GridView1.SelectedRow;
lblTitle.Text = row.Cells[1].Text;
lblBody.Text = row.Cells[2].Text;
}
finally
{
// Close Connection
thisConnection.Close();
}
}
<asp:Panel ID="pnlView" runat="server" Visible="False" CssClass="pnlView">
<h1 style="background-color: #CCE6FF">
<asp:Label ID="lblTitle" runat="server" Text="Label"></asp:Label></h1>
<p>
<asp:Label ID="lblBody" runat="server" Text="Label"></asp:Label></p>
<p style="background-color: #EFEFEF">
<asp:Button ID="btnEdit" runat="server" Text="Edit This Document" OnClick="btnEdit_Click" /> or
cancel</p>
</asp:Panel>
Your code shows that you have a table filled with documents IDs (I'm assuming you already bound this correctly in some other part of your code)
You used the SelectedIndexChanged event correctly, but why are you executing another query inside? If executing the query is intentional, why are you binding your labels to old data? row.cells[].value holds old information, not the information you re-queried. If you want the information you re-queried for, get that info directly from the result set. Also, that result set will return nothing unless you change nonqueryCommand.ExecuteNonQuery(); to nonqueryCommand.ExecuteDataSet();
Reiterating what User:rkw mentioned above. ExecuteNonQuery will not get any data back from the database. You need to use either DataReader ( http://msdn.microsoft.com/en-us/library/haa3afyz.aspx ) or use the ExecuteDataset and put the information into a DataSet and then read from it. When you say ExecuteNonQuery you are basically telling the SQL server to execute commands but not expecting any data back from the SQL Server.

How would I search through a DataSet and change data in it before it gets bound?

Try
Dim ds As DataSet = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings("connstr").ConnectionString, CommandType.StoredProcedure, "Get_All", New SqlParameter("#userid", Session("userid")))
rptBundles.DataSource = ds.Tables(0)
Catch ex As Exception
showerror(ex, Me)
End Try
That is the code, I want to be able to parse through it and find certain rows that have a certain boolean set to 1 and then edit other variables in that row accordingly, how would I do this, I tried making a For Each row nested in a For Each table but when I tested it the repeater never populates with data...
For Each ds_table As DataTable In ds.Tables
For Each ds_row As DataRow In ds_table.Rows
If ds_row("isScorm") = 1 Then
ds_row("totalLessons") = 100
ds_row("TotalQuestions") = 100
ds_row("lessonscompleted") = 50
ds_row("TotalCorrect") = 50
End If
Next
Next
Only when I remove that code does the repeater populate as expected, but I got no errors!
If you're using a Repeater, or whatever datasource bound control, I would use the ItemDataBound event and set those values to your controls.
If this was your basic HTML
<html>
<asp:Repeater id="repeater" runat="server" OnItemDataBound="repeater_ItemDatabound">
<ItemTemplate>
<span><%# DataBinder.Eval(Container.DataItem, "isScorm") %></span>
<span id="totalLessonsSpan" runat="server"><%# DataBinder.Eval(Container.DataItem, "totalLessons") %></span>
</ItemTemplate>
</asp:Repeater>
</html>
I would have this in the code behind
protected void repeater_ItemDatabound(object sender, RepeaterItemEventArgs e)
{
DataRow row = e.Item.DataItem as DataRow;
if (row == null) { }
else
{
int isScorm = 0;
int.TryParse(Convert.ToString(row["isScorm"]), out isScorm);
if (isScorm > 0)
{
HtmlGenericControl totalLessonsSpan = e.Item.FindControl("totalLessonsSpan") as HtmlGenericControl;
totalLessonsSpan.Text = "100";
}
}
}
You probably don't want to loop through the data and swap it there, then bind when you can do it during the bind.
Alternately, something I hate that DB's do because of my need for data integrity, is change it in your SQL select with case statements.
Does adding rptBundles.DataBind() after setting DataSource fix the problem?
Also, you might want to check out the DataTable.Select method to only select (and then modify) rows where isScorm = 1.

Resources