How to sort using GridView and ObjectDataSource? - asp.net

I have a GridView with an ObjectDataSource and I want to be able to sort it.
Paging works correctly, however Sorting gives me an exception:
The GridView gridView fired event Sorting which wasn't handled.
How do I enable sorting on the server side?
(i.e. gridView.EnableSortingAndPagingCallbacks must remains false)

Set the gridView.AllowSorting property to true. From here the grid should allow you to sort data automatically on postback if you are using an object that implements IBindingList. However, since that is most likely not the case, you should take TheTXI's advice above and handle the sorting event yourself. Either wire the GridView.Sorting event in the codebehind, like so:
gridView.Sorting += new GridViewSortEventHandler(gridView_Sorting);
Handle the sorting inside the gridView_Sorting method, which should look like this:
private void gridView_Sorting(object sender, GridViewSortEventArgs e)
{
//Sorting logic here
}
Also, you can wire the event on the page itself using OnSort="gridView_Sorting" attached to the control.
Remember, since you are setting gridView.EnableSortingAndPagingCallbacks to false, this will not be immediately fired when the user tries to sort, it instead will wait for the postback to the server.
I hope this helps!
EDIT:
Since ObjectDataSource seems to be the middleman of choice, here is a brief explanation of wiring that for sorting as well. Use the following in your page (The full example can be found here on the MSDN, near the bottom):
<asp:GridView ID="TestGridView" runat="server" DataSourceID="ObjectDataSourceTest"
AllowSorting="True">
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSourceTest" runat="server"
SelectMethod="SelectMethod"
TypeName="Samples.AspNet.CS.SortingData"
SortParameterName="sortExpression">
</asp:ObjectDataSource>
Instead of actually using the gridView.Sorting event, you'll be jumping over to the ObjectDataSource to take care of the sorting. Once the sort is triggered it should call the method found in SelectMethod in your code behind. Then, inside SelectMethod, you would handle the rebuilding of your GridView object, which would look like:
public void SelectMethod(string sortExpression)
{
//Rebuild gridView table if necessary, same method used in
//on a postback, and retrieve data from the database. Once
//completed sort the data with:
gridView.Sort(sortExpression, SortDirection.(Ascending or Descending))
}

I am using Linq2Sql and a ObjectDataSource and it does Paging and Sorting very well.
I implemented a Class to be used as the ObjectDataSource. It has a Select and a Count method calling my business layer which uses Linq2SQL queries to retrieve data from the DB. The select methods gets the first item index, page size and the sort expression as parameters automatically.
public List<EntityClass> Select(int startIndex, int pageSize, string sortBy) {}
public int Count() {}
In the ASPX, the DataSource is configured like this:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="Select" EnablePaging="true"
StartRowIndexParameterName="startIndex"
MaximumRowsParameterName="pageSize"
SortParameterName="sortBy" SelectCountMethod="Count" >
</asp:ObjectDataSource>
The Select and the Count method use Linq queries to retrieve the data from the DB. I use the Skip(), Take() and Orderby() methods. For the OrderBy to accept a string sort expression I use DynamicLinq There is not much to code, Databinding, paging and Sorting are automatically working.

Pass SortExpression in method of Data Access Layer that calls StoredProcedure
and write below way SP to handle sorting on SQL. This way you can improve the performance of your sorting.
Database SP:
Select ROW_NUMBER() OVER(ORDER BY '+#sortExpression +' ) as RowNum
,* from (SELECT CUSTOMERID,
LEDGERDESCRIPTION,
CustomerDescription as CustomerName
WHERE REGIONID ='''+#RegionID+''')t
order by RowNum'

You can use LINQ for this just use OrderBy on the selected column, do this:
public static List<YourDataObject> GetSortedData(string orderBy)
{
List<YourDataObject> sortedDataList = new List<YourDataObject>();
switch (orderBy)
{
case "Col1": sortedEmployeeList = GetDefaultObjects().OrderBy(x => x.Col1).ToList();
break;
case "Col2":
//Do this for all columns
default:
sortedEmployeeList = GetDefaultObjects();
break;
}
return sortedEmployeeList;
}
This is done in the Select method of the object data source.
In the GridView add sort keys in it. And add SelectParameter in the object data source a `orderBy'
<asp:GridView AllowSorting="true" DataSourceID="objDsAllObjects" ....>
<Columns>
<asp:BoundField SortExpression="Col1"/>
<!-- Do this for all columns -->
</Columns>
</asp:GridView >
<asp:ObjectDataSource ID="objDsAllObjects" SortParameterName="orderBy" runat="server"
SelectMethod="GetAllEmployees" TypeName="YourObjectClass"></asp:ObjectDataSource>
It worked for me without changing the DAL layer and stored procedures.

Related

How to construct a lambda for a custom sort for dbContext used in GridView on related tables

I have a GridView using model binding and strongly typed EF5 dbContext classes in a webforms app. The gridview is using the SelectMethod property to return classes from my EF5 model:
<asp:GridView ID="Server_grid" runat="server" AllowSorting="True"
ItemType="server_table" SelectMethod="getServers">
My EF server_table class has a relationship to another table called user_table. In my grid I am pulling the related username property from the user_table for one of my columns like this:
<asp:TemplateField HeaderText="User Name">
<ItemTemplate>
<%# Item.user_table.username %>
</asp:TemplateField>
My select method looks something like:
public IQueryable<server_table> getServers()
{
return dataContext.server_table.Include(x => x.user_table);
}
This all works just fine, but I need to sort on this column. So... I either need to ditch model binding completely (boo!) and select into an anonymous type in my query (in other words go back to using LinqDataSource and anon types and lose the typechecking that model binding gives me, sigh) which will allow me to simply add a simple SortExpression property on the GridView, or... figure out some way to do a custom sort.
So I've been hacking around with that idea and have come up with something that might work. What I'm doing is declaring a sort event like this:
protected void serverSort(object sender, GridViewSortEventArgs e)
{
sortExpression = e.SortExpression; //stash in a global
e.Cancel = true; //don't actually sort
Server_grid.DataBind(); //I'll sort there based on the stashed sortExpression
}
Then in my SelectMethod, I'm trying to build a lambda based on that saved sortexpression, so for example in this case I'd put "user_table.username" in my column SortExpression property, which gets stored in sortExpression above, then parse it and use it for the below:
public IQueryable<server_table> getServers()
{
var query = dataContext.server_table.Include(x => x.user_table);
//how do I use the sortExpression string below to build a lambda?
query = query.OrderBy(SOMETHING CLEVER HERE);
return query;
}
So basically, if I have the name of the table property OR the name of the table relationship with a property name on that related table, how can I build a lambda from that? So if my SortExpression is a simple property name on the server_table, it will work, or if it's a property name on a related table, I could parse the sortExpression string apart to have a related table + property name on that table. I need to be able to parse relationships of any length, i.e. user_table.other_table.another_table.yadday-yadday.property. There's got to be a way but my LINQ internals foo is not so great (any suggestions on a book for that?).
OR is there a better/simpler way to be able to sort like this, and continue using model binding, which I love? I wish I could just use user_table.username in the grid SortExpression property!! Agh!
You can use attribtes collection in order by p => p.Attributes["ColumnName"]
public IQueryable<server_table> getServers()
{
var query = dataContext.server_table.Include(x => x.user_table);
//how do I use the sortExpression string below to build a lambda?
query = query.OrderBy(p => p.Attributes[ColumnNameStoredInSortExpressVariable]);
return query;
}

Pass value from ObjectDataSource object level to ASP .NET Control

I'm working with ObjectDataSource and FormView control.
In ObjectDataSource I have a type specified, and a method that performs Insert operation. However, once Insert is done, I would like to redirect the user to other page, and I need ID of newly created/persisted object.
So in FormView:
OnItemInserted="OnFormItemInserted"
handler I need to access that ID. But how to pass that id gracefully from ObjectDataSource level? I could use
HttpContext.Current.Items
inside ObjectDataSource handling object, but I don't like it, as my handling type do not know anything about ASP .NET (separation of concerns?).
Thanks
Have you tried something simple like this:
In your Data object
Update your insert method configured in the ObjectDataSource control to return the generated ID:
public int Insert(string firstName, int lastname)
{
// return the ID generated by the insert command
return 4;
}
ASPX code behind
And that's it, in your page where you are handling the Inserted event:
protected void ods_Inserted(object sender, ObjectDataSourceStatusEventArgs e)
{
var res = e.ReturnValue.ToString();
this.lblMessage.Text = res;
// add your cool additional logic and redirect stuff
}

Object data source select method returning nothing in code call

I have an ObjectDataSource with the proper SelectMethod and SelectParameters configured. The data source is bound to a grid view which successfully displays the data on page load.
What I need is the ability to rerun the Select method defined by the ObjectDataSource to be stored in a variable and manipulate the items in it. The issue I keep encountering is that calling the .Select() method always returns 0 rows despite it populating the grid view properly.
Is there a reason I can't manually rerun the Select() method on the object data source?
Update 2:
Here is how I setup the ObjectDataSource:
myObjectDataSource.TypeName = typeof(MyDataAccessObject).ToString();
myObjectDataSource.SelectMethod = "GetBy" + stringVariable;
myObjectDataSource.SelectCountMethod = "GetCountBy" + stringVariable;
myObjectDataSource.EnablePaging = true;
Update 1:
I run the Select() on a link button's OnClick event:
protected void LinkButton1_Click(object sender, EventArgs e)
{
SetupDataSource(); // populates the objSource's SelectMethod, SelectParameters and TypeName etc.
var items = objSource.Select();
int count = items.Count(); // returns 0;
}
The ObjectDataSource is setup (SelectMethod and SelectParameters are set) in the Page_Load event.
ObjectDataSource definition:
<asp:ObjectDataSource ID="objSource" runat="server" EnablePaging="True" SortParameterName="sortExpression" ></asp:ObjectDataSource>
GridView definition:
<asp:GridView
ID="myGridView"
runat="server"
DataSourceID="objSource"
AllowPaging="true"
ShowHeader="true"
AutoGenerateColumns="false"
AllowSorting="true"
Width="100%" >
Turns out that underlying data access object's method took pagination into account and was returning .Take(maximumRows) which was always 0.
As a workaround, I programmatically disabled pagination via myObjectDataSource.EnablePaging = false; then created a new function which does not take into account pagination (a new function was required because the ObjectDataSource was looking for a function with specific paremeters).
You don't mention when (in what event you are trying to rebind the to the DataSource)
Short code snippet may help.
If you are in Postback then Gridviews are not re-bound on postback, their rows are pulled back from viewstate. Resetting the gridview's DatasourceID to the object data source ID on page load (or init?) will cause the gridview to be rebound.
I had similar problem before. I think .Select() is implemented using DataReader and once Select has been called once the reader is empty so any subsequent call to .Select or .Count() will return empty result.
Therefore, what you can do is to use .ToList() to store the result in a list and then just keep reusing the list.

Confused about databinding in ASP.NET

Lets say I have an object
class Person {
public string Name { get; set; }
public int Age { get; set; }
}
And that object is retrieved from a Factory (ie Can't use SQLDataSource or anything like that)
Person person = PersonFactory.GetPerson();
How can I two-way DataBind the two properties to Textboxes on a web form? I looked into FormView, but that doesn't seem to fit my needs as I am not iterating over a collection of objects. And when I tried to use it, I don't seem to be getting the posted values in the Person object in the FormUpdated event. And I am binding like this
Markup
<asp:Textbox Text=<%# Bind("Name") %> />
Code behind
FormView1.DataSource = new List() { person };
FormView1.DataBind();
I feel like I am missing something really obvious. Should I be using a FormView? It doesn't seem like it a proper fit for simple data binding, but the <#% Bind %> method must be in some type of container -- is there a more suitable object?
You need to handle the updates to your FormView - the updates in the asp.net databound controls are not automatic. I'd also consider using an ObjectDataSource - keeping your binding all in the markup can make things easier to find. When you use the ObjectDataSource - it'll automatically wrap your single object in an IEnumerable, so binding to a method that returns a Person is acceptable. You could also consider using a DetailsView if you don't want to write out the form yoruself. In your case, you could do the following
<asp:FormView runat="server" DataSourceID="MyPersonDataSource"> ... </asp:FormView>
<asp:ObjectDataSource runat="server" ID="MyPersonDataSource"
TypeName="PersonFactory" DataObjectTypeName="Person"
SelectMethod="GetPerson" UpdateMethod="UpdatePerson" />
And to facilitate this, you'd need an UpdateMethod(Person) method on your PersonFactory class. Doing it this way eliminates your binding from the codebehind, and will allow your updates to your person object to be persisted to your data store without you having to handle the update events yourself.
Try calling the DataBind method on your TextBox controls.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
MyTextBox1.DataBind();
MyTextBox2.DataBind();
}
I've never tried doing two way binding in exactly this way before, but using the Bind("property") syntax, it should work this way as far as I know.
If calling the DataBind method doesn't work, then the FormView is your best bet.
DataBinder.Eval(Container.DataItem, "Name")
This should do the trick, but I think you need some event-handling of your own to do the 2-way binding.

Reading data from BaseDataBoundControl.DataSource (ASP.NET GridView)

I have an ASP.NET 3.5 GridView on a WebForm.
The GridView gets data from an ObjectDataSource which is set via the DataSourceID property in the code in front.
The ObjectDataSource returns a List of custom data class objects (just a class with public properties) to populate the GridView.
What I want to do is use the a List comsumed by the GridView in another code-behind method. At a high level:
1. GridView is loaded with List data from ObjectDataSource.
2. In the GridView.OnDataBound method I call GridView.DataSource to get the List object.
3. I enumerate the List and use the same data to do some other operation.
The theory being one less duplicated method call and one less call to the back-end database.
I've tried calling DataSource from the GridView' DataBound method and calling GridView.Rows[x].DataItem. In each case I only get a Null reference exception ("Object reference not set to an instance of an object").
Is there any way to achieve what I'm after?
If I understand you correctly, you want the OnRowDataBound event. This way, you can use data from the row that was just databound:
protected void gvGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
CustomDataClass data = e.Row.DataItem as CustomDataClass;
if (data != null)
{
// access data here...
}
}
But do you want the onRowDataBound event? It looks like you want the onDataBound event for the GridView's entire datasource...
So you don't necessarily want one instance (row) of CustomDataClass, you want the entire CustomDataClass[] array of rows to use somewhere else.
HELP! I need this too.
******UPDATE******
I found the answer. Do this as below and set the OnSelected event in your objectdatasource:
protected void ObjectDataSource_Selected(object sender, ObjectDataSourceStatusEventArgs e)
{
ObjectListRow[] objectArray = (ObjectListRow[])e.ReturnValue;
List objectList = objectArray.ToList();
}
It turns out my datasource was an array, but if yours is a List<> then just cast the e.ReturnValue as the List.
EASY CHEESY.

Resources