I have just taken over a project using webforms and C# with my asp.net project. My page load times are very slow and I assume it is due to the multiple database calls that are being made, however I am not sure how to do it any differently. For example, I have one page that holds 3 different dropdownlists each dropdownlist is populated in the Page_Load() event handler, but all 3 have there own database call.
Below is pseducode to show the approach being used. What is the proper way to accomplish something like this?
namespace CEDS
{
public partial class BBLL : System.Web.UI.UserControl
{
private DataSet DS = new DataSet();
private C2 _C2 = new C2();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GetDataForDropDown1();
GetDataForDropDown2();
GetDataForDropDown3();
}
}
private void GetDataForDropDown1()
{
DS = _C2.GetDataForDropDown1();
this.gv1.DataSource = DS;
this.gv1.DataBind();
this.gv1.Visible = true;
}
private void GetDataForDropDown2()
{
DS = _C2.GetDataForDropDown2();
this.gv2.DataSource = DS;
this.gv2.DataBind();
this.gv2.Visible = true;
}
private void GetDataForDropDown3()
{
DS = _C2.GetDataForDropDown3();
this.gv3.DataSource = DS;
this.gv3.DataBind();
this.gv3.Visible = true;
}
}
public class C2
{
private DataSet DS = new DataSet();
private DatabaseAccessLayer DAL = new DatabaseAccessLayer();
public DataSet GetDataForDropDown1()
{
DS = new DataSet();
DAL.SqlQueryBuilder = new StringBuilder();
DAL.SqlQueryBuilder.Append("exec dbo.RunStoredProcedure1 ");
DS = DAL.ExecuteSqlQuery(databaseConnection, DAL.SqlQueryBuilder.ToString());
return DS;
}
public DataSet GetDataForDropDown2()
{
DS = new DataSet();
DAL.SqlQueryBuilder = new StringBuilder();
DAL.SqlQueryBuilder.Append("exec dbo.RunStoredProcedure2 ");
DS = DAL.ExecuteSqlQuery(databaseConnection, DAL.SqlQueryBuilder.ToString());
return DS;
}
public DataSet GetDataForDropDown3()
{
DS = new DataSet();
DAL.SqlQueryBuilder = new StringBuilder();
DAL.SqlQueryBuilder.Append("exec dbo.RunStoredProcedure3 ");
DS = DAL.ExecuteSqlQuery(databaseConnection, DAL.SqlQueryBuilder.ToString());
return DS;
}
}
public class DatabaseAccessLayer
{
public DataSet ExecuteSqlQuery(string connectionString, string sqlQuery)
{
try
{
_connectionString = System.Configuration.ConfigurationManager.AppSettings[connectionString].ToString();
_sqlDatabaseConnection = new SqlConnection(_connectionString);
_sqlCommand = new SqlCommand(sqlQuery, _sqlDatabaseConnection);
_sqlDatabaseConnection.Open();
_sqlCommand.CommandTimeout = 0;
_dataSet = new DataSet();
_sqlDataAdapter = new SqlDataAdapter(_sqlCommand);
_sqlDataAdapter.Fill(_dataSet, "Data");
return _dataSet;
}
catch (Exception exception) { throw exception; }
finally
{
_sqlDatabaseConnection.Close();
_sqlCommand.Dispose();
_sqlDataAdapter.Dispose();
}
}
}
}
Page_Load()
{
var t1 = GetDataForDropDown1();
var t2 = GetDataForDropDown2();
var t3 = GetDataForDropDown3();
await Task.WhenAll(t1, t2, t3);
PopulateDD1();
PopulateDD2();
PopulateDD3();
}
async Task GetDataForDropDown1()
{
SqlQuery
Call To Database Access Layer
await Execute Stored Procedure
Store Returned Result In Dataset
}
async Task GetDataForDropDown2()
{
SqlQuery
Call To Database Access Layer
await Execute Stored Procedure
Store Returned Result In Dataset
}
async Task GetDataForDropDown3()
{
SqlQuery
Call To Database Access Layer
await Execute Stored Procedure
Store Returned Result In Dataset
}
You should be able to do
Parallel.Invoke(GetDataForDropDown1, GetDataForDropDown2, GetDataForDropDown3);
So at least you aren't waiting for the first to complete until you start waiting for the second and third.
It might be more effective to have a single stored procedure that returns all three recordsets, so your database connection and retrieval roundtrip is only made once. But that will probably mean you have to change your data layer code.
The better approach might be this, try it.
Page_Load()
{
if(!Page.IsPostBack)
{
LoadData();
}
}
LoadData()
{
// PopulateDropDown1 Code
// PopulateDropDown2 Code
// PopulateDropDown3 Code
}
if(!Page.IsPostBack) prevents LoadData() to call on every postback.
Related
How can I pass retrieve class variable value to the checkIn form?
checkIn form: my question is: why is the oldCheckInDt variable still empty?
// assign variable
DataTable oldCheckInDt = new DataTable();
private void btnUpdateCheckIn_Click(object sender, EventArgs e)
{
Retrieve.loadDataTable("sp_loadBasicCheckIn", oldCheckInDt, Convert.ToInt32(txtCheckInId.Text.Trim()));
// my question - why is "oldCheckInDt"// still empty
}
Retrieve class:
public static void loadDataTable(string proc, DataTable tb, Int32 Id)
{
try
{
SqlCommand cmd = new SqlCommand(proc, MainClass.con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#id", Id);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
if (dt.Rows.Count > 0)
{
tb = dt; // tb successfully filled with data.
}
else
{
tb = null;
}
}
catch (Exception ex)
{
MainClass.showMessage("Exception : " + ex, "error");
}
}
When you pass a DataTable in that way, the receiving method creates a new variable (called tb) in its stack and assign at that variable the reference to the memory where the line DataTable oldCheckInDt = new DataTable(); created the initial table.
Now when you write tb = dt; you are changing the value of tb (it contains a reference) to a different reference (the value of dt) but the initial oldCheckInDt is unaffected by this operation. It still contains the original reference.
This is key point in programming. Understanding the difference between values and references is fundamental See: Values vs Reference and many more articles about this point.
However, to fast fix your code I think you should return the DataTable loaded in the loadDataTable method instead of trying to assign the loaded table to a parameter passed from the caller.
public static DataTable loadDataTable(string proc, Int32 Id)
{
try
{
using(SqlConnection con = new SqlConnection(MainClass.Connectionstring))
{
con.Open();
SqlCommand cmd = new SqlCommand(proc, con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#id", SqlDbType.Int32).Value = Id;
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
return dt;
}
}
catch (Exception ex)
{
MainClass.showMessage("Exception : " + ex, "error");
return null;
}
}
Then in the calling code you could write
// assign variable
DataTable oldCheckInDt = new DataTable();
private void btnUpdateCheckIn_Click(object sender, EventArgs e)
{
oldCheckInDt ? Retrieve.loadDataTable("sp_loadBasicCheckIn", oldCheckInDt, Convert.ToInt32(txtCheckInId.Text.Trim()));
if(oldCheckInDt!= null && oldCheckInDt.Rows.Count > 0)
{
....
}
else
{
....
}
}
Notice other changes made to the loadDataTable method. First of all is a common error to use a global connection object and keep it open for the lifetime of an application. Create/Use/Destroy is the best way to preserve resources and let the ADO.NET Connection Pooling do its dirty work for you to serve connections. Second, do not use AddWithValue because it has a subtle behavior with parameters of type Date, Decimals/Float or event strings. Better avoid it
I am running a SQLQuery that takes roughly 45 seconds to run and display results. I am using Task<DataSet> to populate two drop downs on my page. Well the 1st drop down populates fine (the query is completed in about 2 seconds), the second it seems that the adapter.Fill(dataSet) is not waiting on the query to complete before it begins to fill the drop down with a null dataset. What should I alter so that the code execution halts until the query executes completely?
Task.Factory.ContinueWhenAll(new[]
{
One("Data Source=server;Initial Catalog=db;Integrated Security=True;MultipleActiveResultSets=True"),
Two("Data Source=server;Initial Catalog=db;Integrated Security=True;MultipleActiveResultSets=True"),
}, tasks =>
{
try
{
this.ddl1.DataSource = tasks[0].Result.Tables[0];
this.ddl1.DataTextField = "One";
this.ddl1.DataValueField = "ID";
this.ddl1.DataBind();
int indexOfLastItem = this.ddl1.Items.Count - 1;
this.ddl1.SelectedIndex = indexOfLastItem;
ddl2.DataSource = tasks[1].Result.Tables[0];
this.ddl2.DataTextField = "Two";
this.ddl2.DataValueField = "ID";
this.ddl2.DataBind();
this.ddl2.Items.Insert(0, new ListItem(Constants.All, Constants.All));
}
catch (Exception exception) { throw exception; }
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
public System.Threading.Tasks.Task<DataSet> One(string databaseConnection)
{
return FillDS("Select * from activeemployees", databaseConnection);
}
public System.Threading.Tasks.Task<DataSet> Two(string databaseConnection)
{
return FillDS("Select * from mastersalesdatabase", databaseConnection);
}
public System.Threading.Tasks.Task<DataSet> FillDS(string sqlQuery, string connectionString)
{
try
{
var dataSet = new DataSet();
using (var adapter = new SqlDataAdapter(sqlQuery, connectionString))
{
adapter.Fill(dataSet);
return dataSet;
}
}
catch (Exception exception) { throw exception; }
}
My query Select * from activeemployees completes in about 2 seconds and populates fine, my query Select * from mastersalesdatabase takes roughly 45 seconds and it seems the code just moves on w/o a delay to let the query execute to completion.
If you're going to do async to retrieve data into a datatable, it should look more like this:
public static async Task<DataTable> GetDataTableAsync(string connectionString, SqlCommand command)
{
using (var connection = new SqlConnection(connectionString))
{
command.Connection = connection;
await connection.OpenAsync();
using (var dataReader = await command.ExecuteReaderAsync())
{
var dataTable = new DataTable();
dataTable.Load(dataReader);
return dataTable;
}
}
}
Notice there's no need for a dataset.
Then in WebForms, we have to handle async code differently.
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(DoWorkAsync));
}
private async Task DoWorkAsync()
{
ActiveEmployeesDropDownList.DataSource = GetDataTableAsync(databaseConnection, new SqlCommand("select * from activeemployees"));
ActiveEmployeesDropDownList.DataBind();
}
Notice I renamed the control from ddl1 to ActiveEmployeesDropDownList because ddl1 is a horrible name. Your names should have semantic meaning.
You'll need to add the async=true attribute to your page according to MSDN.
And you should also fix your query to not take 45 seconds, but that's a separate question entirely.
I am learning C# and SQL Server. I was following a simple tutorial, which led me to the creation of a class DBconnection that connects and updates the DB. Then I have a simple C# form that navigates back and forth on table rows using a button and a DataSet to clone the data, and then displays the info on some labels.
No problem 'till here, but then I thought, what if I wanted to display a single value(column) of a specific row, say "show me the last name of the person with a certain first name".
I'm familiar with the SQL query commands, so what I want is something like this:
SELECT last_name FROM Employees WHERE first_name = 'Jason'
Follows my code...
DBconnection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cemiGEST
{
/// <summary>
/// A class that makes the connection to the SQL Database
/// </summary>
class DBconnection
{
// variables
private string sql_string;
private string strCon;
System.Data.SqlClient.SqlDataAdapter da_1;
// set methods
public string Sql
{
set { sql_string = value; }
}
public string connection_string
{
set { strCon = value; }
}
// DataSet
public System.Data.DataSet GetConnection
{
get { return MyDataSet(); }
}
// MyDataSet method
private System.Data.DataSet MyDataSet()
{
System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(strCon);
con.Open();
da_1 = new System.Data.SqlClient.SqlDataAdapter(sql_string, con);
System.Data.DataSet dat_set = new System.Data.DataSet();
da_1.Fill(dat_set, "Table_Data_1");
con.Close();
return dat_set;
}
// Update DB method
public void UpdateDB(System.Data.DataSet ds)
{
System.Data.SqlClient.SqlCommandBuilder cb = new System.Data.SqlClient.SqlCommandBuilder(da_1);
cb.DataAdapter.Update(ds.Tables[0]);
}
}
}
I am accessing the values (when moving back and forth) by just incrementing by one a variable that updates the row number. Some exemplifying code follows.
public partial class Example : Form
{
// variables
DBconnection objConnect;
string conStringAUTH;
DataSet ds;
DataRow dr;
int maxRows;
int inc = 0;
private void Login_Load(object sender, EventArgs e)
{
CloseBeforeLogin = true;
try
{
objConnect = new DBconnection();
conStringAUTH = Properties.Settings.Default.authConnectionString;
objConnect.connection_string = conStringAUTH;
objConnect.Sql = Properties.Settings.Default.authSQL;
ds = objConnect.GetConnection;
maxRows = ds.Tables[0].Rows.Count;
if (maxRows == 0)
{
MessageBox.Show("No user found. Loading first run wizard.");
NewUser newUser = new NewUser();
newUser.ShowDialog();
}
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
}
}
I'm sure this is simple, but I'm not getting there.
EDIT:
I would prefer using my class DBconnection and without external libraries. I'm not at the PC with the project, so can't test anything right now, but after a good night of sleep over the matter, and after (re)looking at my code, I may have found the answer. Please tell me if you think this would do the trick:
In my second class (where I connect to and access the DB), I have already using a SQL query, more specifically here:
objConnect.Sql = Properties.Settings.Default.authSQL;
This query (authSQL), was created by me and embedded on the Settings, and here I am importing it. So, if I do the following instead, do you think it would work:
objConnect.Sql = "SELECT last_name FROM Employees WHERE first_name = 'Jason'";
The "Properties.Settings.Default.authSQL" code is nothing more than a shortcut to a string "SELECT * FROM AUTH" - AUTH is my table, which I called Employees for the sake of simplicity.
So, it would be something like this:
public partial class Example : Form
{
// variables
DBconnection objConnect;
string conStringAUTH;
DataSet ds;
DataRow dr;
int maxRows;
int inc = 0;
private void Login_Load(object sender, EventArgs e)
{
CloseBeforeLogin = true;
try
{
objConnect = new DBconnection();
conStringAUTH = Properties.Settings.Default.authConnectionString;
objConnect.connection_string = conStringAUTH;
objConnect.Sql = "SELECT last_name FROM Employees WHERE first_name = 'Jason'";
ds = objConnect.GetConnection;
// Data manipulation here
}
catch (Exception err)
{
MessageBox.Show(err.Message);
}
}
There's no need for a dataset here. If you know the sql you want: use it - perhaps with some parameterization. For example, using "dapper", we can do:
string firstName = "Jason";
var lastNames = con.Query<string>(
"SELECT last_name FROM Employees WHERE first_name = #firstName",
new { firstName }).ToList();
Check this ....hope this also works...
//String Declaration
string Sqlstr = "select CountryCode,Description,Nationality from ca_countryMaster where isDeleted=0 and CountryCode = 'CAN' ";
string DBCon = "Data Source=RAJA;Initial Catalog=CareHMS;Integrated Security=True;";
SqlConnection SqlCon = new SqlConnection(DBCon);
SqlDataAdapter Sqlda;
DataSet ds = new DataSet();
try
{
SqlCon.Open();
Sqlda = new SqlDataAdapter(Sqlstr, SqlCon);
Sqlda.Fill(ds);
gdView.DataSource = ds.Tables[0];
gdView.DataBind();
}
catch (Exception ex)
{
lbl.text = ex.Message;
}
finally
{
ds.Dispose();
SqlCon.Close();
}
Thanks, i need some help, and theres error for DBOperation dbo = new DBOperation(); which they say type or namespace cannot be found.
public partial class Survey : System.Web.UI.Page
{
public DataTable fillmydropdownlist()
{
DataTable drpdt = new DataTable();
SqlConnection con= new SqlConnection();
try
{
con.ConnectionString = #"SurveyFdBk_DB";
con.Open();
string q = "SELECT * FROM [Survey]";
SqlCommand cmd = new SqlCommand(q,con);
SqlDataAdapter da2 = new SqlDataAdapter(cmd);
return drpdt;
}
catch { }
finally{ con.Close(); }
}
protected void Page_Load(object sender, EventArgs e)
{
DBOperation dbo = new DBOperation();
DataTable dt = new DataTable();
dt = dbo.fillmydropdownlist();
DataTable drpdt= new DataTable();
if (dt.Rows.Count > 0)
{
DropDownList1.DataSource = drpdt;
DropDownList1.DataTextField="SurveyName";
DropDownList1.DataValueField="SurveyID";
DropDownList1.DataBind();
}
}
}
All the paths of execution must return something. In your method above there are 2 paths:
public DataTable fillmydropdownlist()
{
try
{
//path 1
return drpdt;
}
catch
{
//path 2
return null; //need return value here
}
}
In the event of an exception being thrown you need to return some sort of value, perhaps null?
That being said catching all errors with no logging or handling is not an advisable practice. You should consider adding some error handling and you should dispose of your DataAdapter in your finally block as well.
StackOverflowException was unhandled. I need help on this. I get the error on the line
adp.Fill(ds)
Also I'm not sure why, but I can't remove throw, it says not all codes returning a value.
string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["dbCustConn"].ToString();
string cmdStr = "Select * from MainDB";
public DAL() // default parameter. Use?
{
}
public DataTable Load() // what is this for? (loads all the records from the database)
{
SqlConnection conn = new SqlConnection(connStr);
//SqlCommand cmd = new SqlCommand(cmdStr, connStr);
SqlDataAdapter adp = new SqlDataAdapter(cmdStr, connStr); // SqlDataAdapater? Load all?
DataSet ds = new DataSet();
try
{
adp.Fill(ds);
return ds.Tables[0];
}
catch
{
throw;
}
finally
{
ds.Dispose();
adp.Dispose();
conn.Close();
conn.Dispose();
}
}
public DataTable Load() // what is this for? (loads all the records from the database)
{
SqlDataAdapter adp = new SqlDataAdapter(cmdStr, connStr); // SqlDataAdapater? Load all?
DataSet ds = new DataSet();
using(SqlConnection conn = new SqlConnection(connStr))
{
adp.Fill(ds);
return ds.Tables[0];
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindGrid();
}
}
private void BindGrid()
{
MasterCust.DataSource = GridDataSource();
MasterCust.DataBind();
//DetailCust.DataSource = ds2;
//DetailCust.DataBind();
}
private DataTable GridDataSource()
{
BAL p = new BAL();
DataTable dTable = new DataTable();
try
{
dTable = p.Load();
}
catch (StackOverflowException ee)
{
string message = ee.Message.ToString();
}
finally
{
p = null;
}
return dTable;
}
First, I think the issue is probably in MasterCust. I think that however that is defined may be causing your issues. If you update your question on how this is defined, that may shed some additional light.
Second, you have a lot of extraneous code that could be confusing the issue. Here is what I think that you need to do to pare it down the bare minimum:
protected void Page_Load(object sender, EventArgs e)
{
try
{
if (!IsPostBack)
{
BindGrid();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
// Note that this is for debug purposes only. Production code should log
// this exception somewhere so that it can be observed and dealt with
}
}
private void BindGrid()
{
MasterCust.DataSource = BAL.Load();
MasterCust.DataBind();
}
Then your business access class:
public class BAL
{
private static string connStr = System.Configuration.ConfigurationManager.ConnectionStrings["dbCustConn"].ToString();
private static string cmdStr = "Select * from MainDB";
public static DataTable Load() // what is this for? (loads all the records from the database)
{
using (var adp = new SqlDataAdapter(cmdStr, connStr))
{
var ds = new DataSet();
adp.Fill(ds);
return ds.Tables[0];
}
}
}