How to get my datasource only once for combobox? - c#

I use telerik:RadComboBox
Like this :
<telerik:RadComboBox runat="server" ID="RadComboBox1" EnableLoadOnDemand="true"
ShowMoreResultsBox="true" EnableVirtualScrolling="true" CollapseDelay="0" Culture="ar-EG" ExpandDelay="0" Filter="StartsWith" ItemsPerRequest="100"
MarkFirstMatch="true" Skin="Outlook" ValidationGroup="L" Width="202px" EnableAutomaticLoadOnDemand="True"
EmptyMessage="-Enter user name-"
EnableItemCaching="true" >
<WebServiceSettings Path="../WebService/Employees.asmx" Method="LoadData" />
and my web service :
[System.Web.Script.Services.ScriptService]
public class Employees : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public RadComboBoxData LoadData(RadComboBoxContext context)
{
RadComboBoxData result = new RadComboBoxData();
DataTable dt = FollowsDAL.GetAllEmployees();
var allEmployees = from r in dt.AsEnumerable()
orderby r.Field<string>("name")
select new RadComboBoxItemData
{
Text = r.Field<string>("name").ToString().TrimEnd()
};
string text = context.Text;
if (!String.IsNullOrEmpty(text))
{
allEmployees = allEmployees.Where(item => item.Text.StartsWith(text));
}
//Perform the paging
// - first skip the amount of items already populated
// - take the next 10 items
int numberOfItems = context.NumberOfItems;
var employees = allEmployees.Skip(numberOfItems).Take(100);
result.Items = employees.ToArray();
int endOffset = numberOfItems + employees.Count();
int totalCount = allEmployees.Count();
//Check if all items are populated (this is the last page)
if (endOffset == totalCount)
result.EndOfItems = true;
//Initialize the status message
result.Message = String.Format("Items <b>1</b>-<b>{0}</b> out of <b>{1}</b>",
endOffset, totalCount);
return result;
}}
My problem is :
Although this control is so fast , every time i enter specific name firstly it fetches the 20000 employee in the datatable dt !!!
with every character .
My question is:
How it 's fast like this with this bad behavior?
Is there some way to get all the employees only once ?
How to enhance the performance?

It is always better to use server side filtering, because you do not need to retreive 20000 records to the webserver to use 10 or 20 items to return.
http://demos.telerik.com/aspnet-ajax/combobox/examples/populatingwithdata/autocompletesql/defaultcs.aspx

Your DAL should have a method to filter the results based on the sent text, then you add them to the combobox. My DAL is Telerik OpenAccess ORM (Linq2SQL) but you could also write a stored procedure to filter the results as well.
Here is an example of one of my asmx services that populates a radcombobox:
[WebMethod]
public RadComboBoxData FindEmployee(RadComboBoxContext context)
{
RadComboBoxData comboData = new RadComboBoxData();
using (DataBaseContext dbc = new DataBaseContext())
{
IQueryable<Employee> Employees = dbc.FindEmployee(context.Text);
int itemOffset = context.NumberOfItems;
int endOffset = Math.Min(itemOffset + 10, Employees.Count());
List<RadComboBoxItemData> result = new List<RadComboBoxItemData>();
var AddingEmployees = Employees.Skip(itemOffset).Take(endOffset - itemOffset);
foreach (var Employee in AddingEmployees)
{
RadComboBoxItemData itemData = new RadComboBoxItemData();
itemData.Text = Employee.Person.FullName;
itemData.Value = Employee.EmployeeID.ToString();
result.Add(itemData);
}
comboData.EndOfItems = endOffset == Employees.Count();
comboData.Items = result.ToArray();
if (Employees.Count() <= 0)
comboData.Message = "No matches";
else
comboData.Message = String.Format("Items <b>1</b>-<b>{0}</b> out of <b>{1}</b>", endOffset, Employees.Count());
return comboData;
}
}
and in case you are wondering what my FindEmployee method is:
public IQueryable<Employee> FindEmployee(string SearchString, bool IncludeInactive = false)
{
return from e in this.Employees
where
(e.EmployeeID.ToString() == SearchString ||
e.Person.FirstName.Contains(SearchString) ||
e.Person.MiddleName.Contains(SearchString) ||
e.Person.LastName.Contains(SearchString) ||
(e.Person.FirstName + " " + e.Person.LastName).Contains(SearchString) ||
(e.Person.FirstName + " " + e.Person.MiddleName).Contains(SearchString) ||
(e.Person.FirstName + " " + e.Person.MiddleName + " " + e.Person.LastName).Contains(SearchString)) &&
((e.Inactive == false || e.Inactive == null) && IncludeInactive == false)
select e;
}

According to my understanding, sending request to the Database over and over again for the same purpose is not good for the Application health.
There are basically two ways to make the process fast.
Bring the Data in the form of DataTable from you DataBase.
Bring the Data in the form of DataSet from you DataBase.
DataTable Approach
Fetch all the records from the Database during your Form Load. Preserve it in the ViewState and not in Session. Please take care of this point. Access the Data like below..
Now access the ViewState. Type Cast it and access the below mentioned function.
public static class GetFilteredData
{
public static DataTable FilterDataTable(this DataTable Dt, string FilterExpression)
{
using (DataView Dv = new DataView(Dt))
{
Dv.RowFilter = FilterExpression;
return Dv.ToTable();
}
}
}
DataTableObject.FilterDataTable("Search Expression or your string variable")
This will return you the DataTable. Reassign the data to the control without any DataBase trips. Execute this step whenever you have to filter the records.
DataSet Approach
This process will send 26 DataTable from your database. I know it is looking very heavy. But as you have already mentioned that total records will be 25,000. So, all these records will be divided among these tables. Please see below the explanation.
The ComboBox DataField Text column can have 26 different Start With characters. You have to divide these records according to the Start with character. Record start with A will be inserted into First Table. Records start with B will be inserted into second table, records start with C will be inserted into third table and so on till Record start with Z will be inserted into 26th Table.
Please Note that Your UDT query will originally be used to insert all records in a Local Temporary Table. This Local Temporary Table will further have 26 select statements based upon the Start With Character.
Below is the Sample Stored Proc.
Create Proc ProcName
As
Create Table #Temp
(
ColumnName Varchar(50)
)
Insert into #Temp(ColumnName)
Select ColumnName from YourTableName
Select ColumnName From #Temp Where ColumnName like 'a%'
Select ColumnName From #Temp Where ColumnName like 'b%'
Select ColumnName From #Temp Where ColumnName like 'c%'
--UpTo Z
Now, Finally you have 26 Tables and Data will be returned as DataSet from your BLL.
Preserve it in ViewState only. Now will filtering the data, Please use the below mentioned function.
public static class GetFilteredData
{
public static DataTable FilterDataTable(this DataSet Dt, string FilterExpression)
{
string Lowercase = FilterExpression.ToLower();
Int16 TableID = 0;
if (Lowercase.StartsWith("a"))
{
TableID = 0;
}
else if (Lowercase.StartsWith("b"))
{
TableID = 1;
}
else if (Lowercase.StartsWith("c"))
{
TableID = 2;
}
//upTo Z
using (DataView Dv = new DataView(Dt.Tables[TableID]))
{
Dv.RowFilter = FilterExpression;
return Dv.ToTable();
}
}
}
So what we have understood the significance of using DataSet Technique is that, the records are further divided into Sub Nodes in the for of Tables. Your Search expression will be implemented on Splitted Nodes of DataSet rather then the Original DataSet.
Code Modification as Per mentioned in the Original Query
Add the following in your Web Application/WebSite only.
public static class GetFilteredData
{
public static DataTable FilterDataTable(this DataTable Dt, string FilterExpression)
{
using (DataView Dv = new DataView(Dt))
{
Dv.RowFilter = FilterExpression;
return Dv.ToTable();
}
}
}
Add the following Property in the WebForm itself. The following Property will return you the result set from Database in case the ViewState is null. Otherwise it will return the ViewState preserved data only.
public DataTable Employees
{
get
{
if (ViewState["Employees"] == null)
{
return FollowsDAL.GetAllEmployees();
}
return (DataTable)ViewState["Employees"];
}
set
{
ViewState["Employees"] = value;
}
}
Now you can access this ViewState in your WebForm , where you have Combobox control. As per my understanding you should go for DataSet Approach.
Please note that WebService is not required in this context.

I would create a method that loaded the values from your database and then stored them in cache. Subsequent calls to this method should return the cached version. Then set the DataSource to this method. That should give you a very nice performance boost.
http://msdn.microsoft.com/en-us/library/system.web.caching.cache.aspx

I think your solution should be a mix of answers by #PraVn and #nurgent. Write a stored procedure which filters records by search string. Have your DAL call this SP using a method which in-turn is called from your existing web method public RadComboBoxData LoadData(RadComboBoxContext context)

Related

Best way to check if column exist in the DataReader

We have number of store procedures against each data layer. For an example , we have an Employee table with 20 columns and there are about seven store procedures where this table has been referenced. We have one data binding method used against all employee store procedures. Every time i add a new column in the table, i have to add the column reference to all seven store procedure (even though it is not required in all of them). which is bit pain.
As we are using one data binding method, what would be the best way to make this process more efficient?
What if i add a column reference to just in those sp where it is required and then check during data binding if column exists in the dataReader. I don't want to loop through each row and then loop through all columns to find out if column exists. If i have 1000 rows and 20 columns then it would be a loop of 1000 x 20 which is not very efficient.
Would that be okay if i add dataReader results in ArrayList and then use contain method to find if column exists in the ArrayList?
Here's an extension method I found a while back to check for column existence:
Should note that it's not very efficient.
public static bool HasColumn(this IDataRecord dr, string columnName)
{
for (int i = 0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}
Perhaps you could use it on the first record and cache the results via some boolean values.
Something like the following:
public void test()
{
//DataBrokerSql is my own helper.
using (DataBrokerSql db = new DataBrokerSql(m_ConnString))
{
bool columnsChecked = false;
bool hasFirstName = false;
bool hasLastName = false;
using (DbDataReader reader = db.GetDataReader("Select * From Person"))
{
while (reader.Read())
{
//Only check for columns on the first row.
if (!columnsChecked)
{
hasFirstName = reader.HasColumn("FirstName");
hasLastName = reader.HasColumn("LastName");
columnsChecked = true;
}
if (hasFirstName)
{
//Read FirstName
var firstName = reader["FirstName"];
}
if (hasLastName)
{
//Read LastName
var lastName = reader["LastName"];
}
}
}
}
}

use select statement to get Data from a DataTable

I have DataTable containing three columns, Name, Date and DialedNumber. I want to get rows on the basis of DialedNumber column having phone number like 03001234567 ...
I am filing datatable with an method return type is datatable.
{
DataTable dt = filldata();
}
Problem is how to use select statement to get rows having number 03001234567 or some other telephone number ?
Try this Suppose you have a variable **string str** which is having that telephone number which you want to get from that data table then you can use this
{
DataTable dt = filldata();
DataRow[] resut = dt.Select("DialedNumber ='" + str + "'");
}
It will return you those rows having same telephone number in column DialedNumber.
If you want to filter from the start, not getting all table rows every time, you should adjust your SQL statement:
SELECT * FROM Table WHERE DialedNumber = #dialedNumber
and in C# use SqlCommand.Parameters.AddWithValue(...) to add the #dialedNumber parameter to the query.
Try to use Linq to DataTable like this
var results = from myRow in dt.AsEnumerable()
where myRow.Field<String>("DialedNumber") == "03001234567"
select myRow;
You can use Linq to DataSet:
string number = "03001234567";
var rows = dt.AsEnumerable()
.Where(r => r.Field<string>("DialedNumber").Contains(number));
You even can project rows into strongly typed objects:
var people = from r in dt.AsEnumerable()
where r.Field<string>("DialedNumber").Contains(number)
select new {
Name = r.Field<string>("Name"),
Date = r.Field<DateTime>("Date"),
DialedNumber = r.Field<string>("DialedNumber")
};
Note: if you want to check exact match of dialed number, then instead of Contains(number) (which is equivalent of LIKE) use == number.
Try like this
private void GetRowsByFilter()
{
DataTable table = DataSet1.Tables["Table1"];
// Presuming the DataTable has a column named Date.
string expression;
expression = "DialedNumber ='03001234567 '";
DataRow[] foundRows;
// Use the Select method to find all rows matching the filter.
foundRows = table.Select(expression);
// Print column 0 of each returned row.
for(int i = 0; i < foundRows.Length; i ++)
{
Console.WriteLine(foundRows[i][0]);
}
}
DataTable.Select Method

DataTable find or if not found insert row

I have a DataTable dt with 2 columns. First col (call it CustomerId) is unique and doesn't allow nulls. the second one allows nulls and is not unique.
From a method I get a CustomerId and then I would like to either insert a new record if this CustomerId doesn't exist or increment by 1 what's in the second column corresponding to that CustomerId if it exists.
I'm not sure how I should approach this. I wrote a select statement (which returns System.Data.DataRow) but I don't know how to test whether it returned an empty string.
Currently I have:
//I want to insert a new row
if (dt.Select("CustomerId ='" + customerId + "'") == null) //Always true :|
{
DataRow dr = dt.NewRow();
dr["CustomerId"] = customerId;
}
If the datatable is being populated by a database. I would recommend making the customerid a identity column. That way when you add a new row it will automatically create a new customerid which will be unique and 1 greater than the previous id (depending on how you setup your identity column)
I would check the row count which is returned from the select statement. Something like
I would also use string.Format...
So it would look like this
var selectStatement = string.Format("CustomerId = {0}", customerId);
var rows = dt.Select(selectStatement);
if (rows.Count < 1){
var dr = dt.NewRow();
dr["CustomerId"] = customerId;
}
This is my method to solve similar problem. You can modify it to fit your needs.
public static bool ImportRowIfNotExists(DataTable dataTable, DataRow dataRow, string keyColumnName)
{
string selectStatement = string.Format("{0} = '{1}'", keyColumnName, dataRow[keyColumnName]);
DataRow[] rows = dataTable.Select(selectStatement);
if (rows.Length == 0)
{
dataTable.ImportRow(dataRow);
return true;
}
else
{
return false;
}
}
The Select Method returns an array of DataRow objects. Just check if its length is zero (it's never null).
By the way, don't write such statements in the code directly as in this example. There's a technique for breaching your code's security called "SQL Injection", I encourage you to read the Wikipedia Article. In brief, an experienced user could write SQL script that gets executed by your database and potentially do harmful things if you're taking customerId from the user as a string. I'm not experienced in database programming, this is just "general knowledge"...

How to query a DataTable in memory to fill another data table

I am trying to update a Microsoft report. What it does is write out how many clients where excluded from a conversion process and for what reason. Currently the program writes all of the deleted clients back to the server then queries it back to fill a specialty table with the results.
Here is the current query:
SELECT DeletedClients.Reason,
COUNT(DeletedClients.Reason) AS Number,
CAST(CAST(COUNT(DeletedClients.Reason) AS float)
/ CAST(t.Total AS float)
* 100 AS numeric(4, 1)) AS percentage
FROM DeletedClients CROSS JOIN
(SELECT COUNT(*) AS Total
FROM DeletedClients AS DeletedClients_1
WHERE (ClinicID = #ClinicID)) AS t
WHERE (DeletedClients.ClinicID = #ClinicID)
AND (DeletedClients.TotalsIdent = #ident)
GROUP BY DeletedClients.Reason, t.Total
ORDER BY Number DESC
What I would like to do is not write DeletedClients to the server as it already exists in memory in my program as a DataTable and it is just slowing down the report and filling the database with information we do not need to save.
My main question is this, Either :
How do I query a data table to make a new in memory data table that has the same results as if I wrote out the the SQL server and read it back in with the query above?
OR
How in Microsoft Reports do you do a group by clause for items in a Tablix to turn =Fields!Reason.Value =Fields!Number.Value =Fields!percentage.Value into something similar to the returned result from the query above?
You can use DataTable.Select to query the DataTable.
DataTable table = GetDataTableResults();
DataTable results = table.Select("SomeIntColumn > 0").CopyToDataTable();
Or for more complex queries, you can use LINQ to query the DataTable:
DataTable dt = GetDataTableResults();
var results = from row in dt.AsEnumerable()
group row by new { SomeIDColumn = row.Field<int>("SomeIDColumn") } into rowgroup
select new
{
SomeID = rowgroup.Key.SomeIDColumn,
SomeTotal = rowgroup.Sum(r => r.Field<decimal>("SomeDecimalColumn"))
};
DataTable queryResults = new DataTable();
foreach (var result in query)
queryResults.Rows.Add(new object[] { result.SomeID, result.SomeTotal });
There are two ways that I can think of to query the data table. Below is an example using both ways.
using System;
using System.Data;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
var deletedClients = GetDataTable();
// Using linq to create the new DataTable.
var example1 = deletedClients.AsEnumerable()
.Where(x => x.Field<int>("ClinicId") == 1)
.CopyToDataTable();
// Using the DefaultView RowFilter to create a new DataTable.
deletedClients.DefaultView.RowFilter = "ClinicId = 1";
var rowFilterExample = deletedClients.DefaultView.ToTable();
}
static DataTable GetDataTable()
{
var dataTable = new DataTable();
// Assumes ClinicId is an int...
dataTable.Columns.Add("ClinicId", typeof(int));
dataTable.Columns.Add("Reason");
dataTable.Columns.Add("Number", typeof(int));
dataTable.Columns.Add("Percentage", typeof(float));
for (int counter = 0; counter < 10; counter++)
{
dataTable.Rows.Add(counter, "Reason" + counter, counter, counter);
}
return dataTable;
}
}
}

how to get distinct records in datatable?

I am using C# + VS2008 + .Net + ASP.Net + IIS 7.0 + ADO.Net + SQL Server 2008. I have a ADO.Net datatable object, and I want to filter out duplicate/similar records (in my specific rule to judge whether records are duplicate/similar -- if record/row has the same value for a string column, I will treat them as duplicate/similar records), and only keep one of such duplicate/similar records.
The output needs to be a datatable, may output the same datatable object if filter operation could be operated on the same datatable object.
What is the most efficient solution?
Are you using .NET 3.5? If you cast your data rows, you can use LINQ to Objects:
var distinctRows = table.Rows.Cast<DataRow>().Distinct(new E());
...
public class E : IEqualityComparer<DataRow>
{
bool IEqualityComparer<DataRow>.Equals(DataRow x, DataRow y)
{
return x["colA"] == y["colA"];
}
int IEqualityComparer<DataRow>.GetHashCode(DataRow obj)
{
return obj["colA"].GetHashCode();
}
}
Or an even simpler way, since you're basing it on a single column's values:
var distinct = from r in table.Rows.Cast<DataRow>()
group r by (string)r["colA"] into g
select g.First();
If you need to make a new DataTable out of these distinct rows, you can do this:
var t2 = new DataTable();
t2.Columns.AddRange(table.Columns.Cast<DataColumn>().ToArray());
foreach(var r in distinct)
{
t2.Rows.Add(r);
}
Or if it would be more handy to work with business objects, you can do an easy conversion:
var persons = (from r in distinct
select new PersonInfo
{
EmpId = (string)r["colA"],
FirstName = (string)r["colB"],
LastName = (string)r["colC"],
}).ToList();
...
public class PersonInfo
{
public string EmpId {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
}
Update
Everything you can do in LINQ to Objects can also be done without it: it just takes more code. For example:
var table = new DataTable();
var rowSet = new HashSet<DataRow>(new E());
var newTable = new DataTable();
foreach(DataColumn column in table.Columns)
{
newTable.Columns.Add(column);
}
foreach(DataRow row in table.Rows)
{
if(!rowSet.Contains(row))
{
rowSet.Add(row);
newTable.Rows.Add(row);
}
}
You could also use a similar strategy to simply remove duplicate rows from the original table instead of creating a new table.
You can do a select into with a group by clause, so not duplicates are created. Then drop the old table and rename the table into which you selected to the original table name.
I would do this in the database layer:
SELECT Distinct...
FROM MyTable
Or if you need aggregates:
SELECT SUM(Field1), ID FROM MyTable
GROUP BY ID
Put the SELECT statement in a stored procedure. Then in .net make a connection to the database, call the stored procedure, execute .ExecuteNonQuery(). Return the rows in a datatable and return the datatable back to your UI.

Categories