Wednesday, June 17, 2009
Using GridView, Entity Framework, LINQ, and an ObjectDataSource to implement a GridView that sorts and filters.
I went in search of an elegant and flexible way to implement a GridView that supports sorting (and the ability to add custom paging if I need it later, though I won’t cover that here) and uses a Data Access Layer (DAL). The filtering I am looking for is the ability to add any number of controls on the page and have them filter the results that are shown in the GridView. Based on the values of these user controls, I want to be able to do a begins with, or a range, or choose from a list of values, etc. I don’t want to be limited to just one value.
I am a believer that like SQL, you don’t want your LINQ queries all over the place. I believe a Data Access Layer (DAL) is a good place to put all your LINQ queries.
At the present time, this means that the ObjectDataSource is probably the best choice because it can call the DAL to do the query and not embed it in the EntityDataSource or LinqDataSource.
It is possible to get pretty good filtering and little to no code to do this using the Dynamic Data Future, but even then I using the DynamicFilter I found it difficult to modify the query to do things like ranges, or a begins with search for example. If you decide to go down that road, you might also want to check out the following post on how to do this in your own project. It makes searching based on a DropDownList or an AutoComplete field very easy. I wanted more flexibility than that. You can also get much of that same functionality from VS 2008 SP1 (yes the SP1 is required to get this functionality since SP1 is essentially a feature release, not a bug fix release).
The hard part of this is writing the DAL method, but is actually much easier than it used to be now that we have LINQ. In this case, I am using LINQ to Entities to query the Entity Framework.
Here is my DAL:
public class DAL
{
private MyEntities ctx = new MyEntities();
public IQueryable GetPerson(string firstName, string lastName, bool hasChildren, int? age, string sortExpr)
{
// set a default sort order
if (string.IsNullOrEmpty(sortExpr))
{
sortExpr = “FName”;
}
var people = from p in ctx.Person
select new
{
ID = p.ID,
FName = p.FName,
LName = p.LName,
Age = p.Age,
NumChildren = p.NumChildren
};
if (hasChildren)
people = people.Where(p => p.NumChildren > 0);
if (!string.IsNullOrEmpty(firstName))
people = people.Where(p => p.firstName.StartsWith(firstName));
if (!string.IsNullOrEmpty(lastName))
people = people.Where(p => p.firstName.StartsWith(lastName));
if (!age.HasValue)
people = people.Where(p => p.Age > age);
var sortedPeople = people.OrderBy(sortExpr)
.Select(“new(ID, FName, LName, Age, NumChildren)”);
return sortedPeople;
}
}
You may notice that the .OrderBy() method gives you a compiler error or is not in your Intellisense. You need to download it from Microsoft. Click here to download. In the zip file, look for the Dynamics.cs file. You can include it in your project or you can build the project that comes with and include the assembly it builds in your project. It is one file, so I like putting it in my project as source code.
This Dynamic class works very in scenarios like this because it actually supports the same syntax as the ObjectDataSource uses which is . If the sort direction is Ascending, then no direction is specifed by the ObjectDataSource. For example the syntax for sorting my FName in Ascending order, the sortExpression would be “FName” or “FName Descending” if you wanted to sort in Descending order.
You may also notice that I use Lambda expressions to do the additional where statements. Be sure to use the value returned by the Where() method since the Where() call doesn’t change (or even query the database). All the Where() does is adds another condition to the existing where clause in the generated sql. Each time Where() is called, the statement is ANDed to the existing where clause. The code is very optimized. I am quite impressed with the code generation.
For related details on sorting with the ObjectDataSource, check out my other post.
Below is the aspx code. The most important thing you get right is the SelectParameters/ControlParameters. The ControlID property needs to match the ID of the Controls you are using for Filtering. The Name property needs to match the parameter names in the DAL method you specified in the ObjectDataSource SelectMethod property.
First Name starts with:
Last Name Starts with:
Has Children:
Posted by Brent V at 2:41 PM 0 comments Links to this post
Labels: .Net, ADO.NET Entity Framework, LINQ, Programming
Monday, June 15, 2009
The GridView ‘GridView1′ fired event Sorting which wasn’t handled.
If you get the error:
The GridView ‘GridView1′ fired event Sorting which wasn’t handled.
You are likely using an ObjectDataSource and then set AllowSorting to true or you are binding to directly your GridView in Page_Load using something like this.
It means you using a GridView that has AllowSorting=”true” equal to true and for some reason nothing has told it what will handle the sorting. The easiest way to avoid this is to use a DataSource control such as an EntityDataSource, SqlDataSource, or LinqDataSource control. The ObjectDataSource will not help you out of the box though. Some extra stuff is required. I’ll show you that later.
This page entry is broken up into to sections. One if you are binding directly to the GridView in your page load and thus have no DataSource assigned to the GridView. Another if you have are using an ObjectDataSource.
In both sections I assume you are using LINQ to access the database, but you could use anything to talk to the database. The logic that needs to be implemented is still the same. I also assume you have an object that encapsulates your database access (a Database Access Layer (DAL)).
For this example, let’s assume you have used the ADO.NET Entity Data Model in Visual Studio to create your entities. In this example we have one entity called Person. It has 3 properties: ID, FName, LName.
Data Access Layer
Below is a solution if you are using an LINQ to Entities, though it would be virtually identical to LINQ to SQL. A similar solution could be used for SQL, though in that case you would translate the requests to SQL statements.
public class DAL
{
private MyEntities ctx = new MyEntities();
public IQueryable GetPerson(string sortExpression)
{
// set a default sort order for when the page is first rendered
if (string.IsNullOrEmpty(sortExpression))
{
sortExpression = “FName Descending”;
}
var people = from p in ctx.Person
select p;
var sortedPeople = people.OrderBy(sortExpression)
.Select(“new(ID, FName, LName)”);
return sortedPeople;
}}
You may notice that the .OrderBy() method gives you a compiler error or is not in your Intellisense. You need to download it from Microsoft. Click here to download. In the zip file, look for the Dynamics.cs file. You can include it in your project or you can build the project that comes with and include the assembly it builds in your project. It is one file, so I like putting it in my project as source code.
This Dynamic class works very in scenarios like this because it actually supports the same syntax as the ObjectDataSource uses which is . If the sort direction is Ascending, then no direction is specifed by the ObjectDataSource. For example the syntax for sorting my FName in Ascending order, the sortExpression would be “FName” or “FName Descending” if you wanted to sort in Descending order.
The GridView sortingEvent also uses very similar syntax. In either case, this saves us from writing a bunch of if-else or switches for each column and sort direction. The choice is yours. This is just so easy, and it is clean.
Binding ObjectDataSource to GridView
This is by far easier of the two methods. I highly recommend using a DataSource such as the ObjectDataSource. The code is much simpler.
To make the ObjectDataSource sort all you have to do is set the DataSourceID property on the GridView to the ID of your ObjectDataSource.
You do have to tell the ObjectDataSource some things about your Data Access Layer though. You need to tell it the type for your Data Access Layer, the method to call, and what the parameter name is for sortExpression the GridView will pass you.
Here is my ObjectDataSource that I defined for the Data Access Layer we defined above.
Binding Directly to GridView in Page Load
If you want to work a little harder you can implement the logic using the GridView and no ObjectDataSource. If you are bind directly to your GridView in your page load, all you to do to stop this error message is handle the Sorting event on your GridView. While this stops the error message, it doesn’t give you sorting when a column header is clicked. You need to put some logic in the Sorting event for it to do something useful.
You are likely binding your DAL to your GridView using something like this or maybe conditionally if it isn’t a postback:
protected void Page_Load(object sender, EventArgs e)
{
GridView1.DataSource = new DAL().GetPerson(“”);
GridView1.DataBind();
}
The GridView does NOT set the SortDirection property in this event handler unless an DataSource object is set. This means that Sorting event ALWAYS will have a e.SortDirection equal to SortingDirection.Ascending. This is a bug in my mind, but I think Microsoft just says it is by design (or bad design if you ask me). For more explanation on this please see here for the response from Microsoft.
As a recommended workaround, we need to track the SortDirection ourselves. In order to do something useful, we need to also track the column that was clicked so that we know when to reset the sort direction to the default direction.
Here is the code to handle the sorting event for GridView. Be sure to wire it up to your GridView.
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
// get values from viewstate
String sortExpression = ViewState["_GridView1LastSortExpression_"] as string;
String sortDirection = ViewState["_GridView1LastSortDirection_"] as string;
// on first time header clicked ( including different header), sort ASCENDING
if (e.SortExpression != sortExpression)
{
sortExpression = e.SortExpression;
sortDirection = “Ascending”;
}
// on second time header clicked, toggle sort
else
{
if (sortDirection == “Ascending”)
{
sortExpression = e.SortExpression;
sortDirection = “Descending”;
}
// Descending
else
{
sortExpression = e.SortExpression;
sortDirection = “Ascending”;
}
}
// save state for next time
ViewState["_GridView1LastSortDirection_"] = sortDirection;
ViewState["_GridView1LastSortExpression_"] = sortExpression;
// NOTE: Depending on the syntax you require for your sortExpression parameter
// to your method, you may need to convert the sort expression to that syntax.
GridView1.DataSource = new DAL().GetPerson(sortExpression + ” ” + sortDirection);
GridView1.DataBind();
}
Posted by Brent V at 12:44 PM 0 comments Links to this post
Labels: .Net, ADO.NET Entity Framework, LINQ, Programming
Thursday, May 14, 2009
ADO.NET Entity Data Model Designer doesn’t open model when double-clicking
I understand that the ADO.NET Entity Data Model Designer is still in its infancy, but I have to wonder how this bug was missed. If you open a model in the ADO.NET Entity Data Model Designer by double-click on it or right-clicking and choosing Open the model opens fine. Close it and try the same thing again. The model will not open.
Luckily the workaround is not too much of a pain. Do the following to get the designer to open the model again.
1. Close the model.
2. Right-click on the Model.edmx (or similarly named file) in the Solution Explorer in Visual Studio 2008.
3. Choose Open With…
4. In the windows that comes up, choose XML Editor and click the OK button. Actually you can also use the Source Code (Text) Editor. You really just need an editor that can read xml, so most of them will work. The key is to open it in something other than the ADO.NET Entity Data Model Designer.
5. Close Model.
6. Double-click on the Model.edmx (or similarly named file) to open it as you usually would.
Unfortunately, you will need to do this every time you close the model and then want to re-open it. It is better than the other workaround of restarting Visual Studio 2008.
Posted by Brent V at 11:49 AM 4 comments Links to this post
Labels: .Net, ADO.NET Entity Framework, Programming
ADO.NET Entity Data Model Designer missing delete functionality
I understand that the ADO.NET Entity Data Model Designer is still in its infancy, but to not delete an object all the way is a BIG missing feature in my opinion.
If you delete an object as I describe below, you may get the following error:
Error 1 Error 3013: Problem in Mapping Fragment starting at line 241: Missing table mapping: Foreign key constraint ‘FK_TestTable1_MyTable’ from table TestTable1 (MyTableID) to table MyTable(ID): no mapping specified for the table TestTable1.
C:\MyWebSite\DataModel\Model.edmx 242 15 DataModel
For simplicity in explanation, let’s assume you have two tables in your model. They are called TestTable1 and MyTable. There is a foreign key / association inTestTable1 that points to MyTable. There is also a reverse association from MyTable to TestTable1.
I decided that I didn’t want TestTable1 in the model anymore. I select the object and delete it. That only deletes it from the viewable portion of the model. It is still in the underlying xml.
You can try to update the model from the database, but it doesn’t show TestTable1 as a table that you can add again. This is because it is still defined in the underlying xml.
Luckily the workaround is pretty easy and painless, but not necessarily obvious at first glance. Do the following to remove the references to TestTable1 and fix the problem.
1. Right-click on the Model.edmx (or similarly named file) in the Solution Explorer in Visual Studio 2008.
2. Choose Open With…
3. In the windows that comes up, choose XML Editor and click the OK button.
4. If you are prompted to close the model because it is already open, click the Yes button.
5. Now, search for your table name. In my case TestTable1. Delete all tags (and inner-Xml) that you find.
6. Save your changes.
7. Right-click on the Model.edmx (or similarly named file) in the Solution Explorer in Visual Studio 2008.
8. Choose Open With…
9. In the windows that comes up, choose ADO.NET Entity Data Model Designer and click the OK button.
10. Rebuild your solution. That should take care of the error.
Another workaround is to actually delete the table from your database. This may or may not be what you want to do. If you do indeed want to delete it from the database also, then you are in luck. You can update the underlying xml by updating the model from the database (right-click on the designer surface).
If you want to see a read-only visual representation of the underlying xml look for a Model Browser tab while you are in the ADO.NET Entity Data Model Designer. Mine is right next to my Solution Explorer on the right side of my screen. If it isn’t there check the different dockable areas. The Model Browser only shows when you are in the ADO.NET Entity Data Model Designer, so be sure that is your active window. It would be nice if you could edit through the Model Browser. Oh well, your choices for now are direct xml editing or modifying your database and synching it.
Posted by Brent V at 11:39 AM 0 comments Links to this post
Labels: .Net, ADO.NET Entity Framework, Programming
Wednesday, May 13, 2009
Making Linq to Entities do a case-insensitive string comparison.
Linq to Entities by default is case-sensitive for string comparisons. Even for SQL Server, which is case insensitive string comparisons are case-sensitive.
Below are two examples. The first example shows a case-insensitive example, and the second one, shows a case-sensitive example.
// This is NOT case-sensitive
using (MyModel ctx = new MyModel())
{
Reviewer reviewer = ctx.Reviewer.First(r => r.FirstName.Equals(“Brent”, StringComparison.CurrentCultureIgnoreCase));
}
// This IS case-sensitive
using (MyModel ctx = new MyModel())
{
Reviewer reviewer = ctx.Reviewer.First(r => r.FirstName == “Brent”);
}
BTW, don’t use the .toLower() method as this translates into a similar call in SQL which then will usually cause your indexes to not be used.
Posted by Brent V at 4:37 PM 0 comments Links to this post
Labels: .Net, ADO.NET Entity Framework, LINQ, Programming
Tuesday, April 28, 2009
AJAX ReorderList breaks when using EntityDataSource entity that has a Navigation Property
After some digging, I figured out the reason why the ReorderList from the AJAX Control Toolkit stopped working for me. When I use the ReorderList with the SqlDataSource it works fine. When I use it with the EntityDataSource it works also. Well sort of. It works fine if the object that you are binding to do not have a Navigation Property in ADO.NET Entity Data Model.
If the object does, you will not receive an error when you reorder items in the ReorderList, but it will not work either. The reason is that the control is not completely robust / completed. If figured this out by changing my reference to the the AjaxControlToolkit.dll that is in the source code version of the Ajax Control Toolkit Sample Application. This allowed me to step through the code. There I saw code that “swallowed” the exception and thus never reported it to the calling method. This is why there is no error, but it is not working either.
Here is the InnerException that I found when I stepped through the ReorderList code:
“Error while setting property ‘ICAContract’: ‘This property descriptor does not support the SetValue method.’.”
at System.Web.UI.WebControls.EntityDataSourceUtil.SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper, Dictionary`2 changedProperties, Boolean overwrite)
at System.Web.UI.WebControls.EntityDataSourceView.InstantiateEntityFromViewState(EntityDataSourceWrapper entityWrapper, IDictionary mergedKeysAndOldValues)
at System.Web.UI.WebControls.EntityDataSourceView.ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
at System.Web.UI.DataSourceView.Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback)
Here is the code from ReorderList.cs file.
try{…}
catch (Exception ex)
{
System.Diagnostics.Debug.Fail(ex.ToString());
//TODO WHY ARE SWALLOWING THIS EXCEPTION!!!
}
I have to conclude that ReorderList needs to handle error properly, and that potentially the ADO.NET Entity Framework may need some more work. I can’t confirm that, but I do know that I have had to implement several work arounds as noted in these blog entries, and that the experience has been buggy at best.
* AJAX ReorderList Example for Adding and Editing Items using the EntityDataSource
* AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource
Posted by Brent V at 4:43 PM 0 comments Links to this post
Labels: .Net, ADO.NET Entity Framework, AJAX, Programming
Monday, April 27, 2009
AJAX ReorderList Example for Adding and Editing Items using the EntityDataSource
This blog entry is very similar to my entry AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource. I highly recommend you read it first to understand the fixes and enhancements I made, since it is the same logic for the EntityDataSource I show here. The functionality is the same, but the difference is that this example show how to use the EntityDataSource (part of the ADO.NET Entity Framework) instead of the SqlDataSource.
The code is very similar to what I did for the SqlDataSource. However, there is some changes that needed to be done as well. The biggest one is that the ReorderList when used with the EntityDataSource requires a DataBind() call after all commands except Edit and Update, and requires special logic for the Update command.
The big change is that there is now a RequiresReorderListDataBind Boolean that I added. This flag is set to true on the initial page load, and then set based on the command that is executed. In turn, when the control is rendered, if RequiresReorderListDataBind is true then DataBind() is called in the PreRender event. You could also call the DataBind() in the appropriate command events such as OnInsertCommand, OnDeleteCommand. However, the Update command needs to call the UpdateItem() and then call DataBind() earlier in the cycle so we put it in the OnItemCommand event. Also, you can’t but the DataBind() call in OnItemCommand for the Insert and Delete. For this reason, I have the logic in the particular events that I do. I wanted to put everything in the OnItemCommand event, but it didn’t work.
This example assumes you have a table in your database. Here is the SQL you can use to create one.
CREATE TABLE [dbo].[TestTable1](
[intID] [int] IDENTITY(1,1) NOT NULL,
[strName] [varchar](50) NOT NULL,
[strLink] [varchar](50) NOT NULL,
[intOrder] [int] NOT NULL,
CONSTRAINT [PK_TestTable1] PRIMARY KEY CLUSTERED
(
[intID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
You will need to add a ADO.NET Entity Data Model (just like you add any other file to your web site). Select the database and table you created above. I called my Model MyModel and my entities MyEntities.
Here is the contents of the .aspx file.
OrderedList AJAX
.ajaxOrderedList li
{
list-style:none;
}
<asp:HyperLink ID="HyperLink1" runat="server" Text='’ NavigateUrl=” />
<asp:Panel ID="dragHandle" runat="server"
style="height: 20px; width: 20px; border: solid 1px black; background-color: Red; cursor: pointer;"
Visible="”>
<asp:TextBox ID="txtName" runat="server" Text='’>
<asp:TextBox ID="txtLink" runat="server" Text='’>
<asp:TextBox ID="txtName" runat="server" Text='’/>
<asp:TextBox ID="txtLink" runat="server" Text='’ />
<asp:TextBox ID="txtOrder" runat="server" Text='’ />
Here is the code-behind (.cs) file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
public partial class EntityDataSourceTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Label3.Text = DateTime.Now.ToLongTimeString();
if (!IsPostBack)
{
ShowDragHandle = true;
RequiresReorderListDataBind = false;
}
}
protected void ReorderList1_ItemReorder(object sender, AjaxControlToolkit.ReorderListItemReorderEventArgs e)
{
ShowDragHandle = true;
}
protected Boolean ShowDragHandle { get; set; }
protected void ReorderList1_ItemCommand(object sender, AjaxControlToolkit.ReorderListCommandEventArgs e)
{
switch (e.CommandName)
{
case “Edit”:
ShowDragHandle = false;
RequiresReorderListDataBind = false;
break;
case “Update”:
ShowDragHandle = true;
ReorderList1.UpdateItem(ReorderList1.EditItemIndex);
ReorderList1.DataBind();
RequiresReorderListDataBind = false;
break;
// Cancel, Insert, Delete, and any unknown case
default:
ShowDragHandle = true;
RequiresReorderListDataBind = true;
break;
}
}
private Boolean RequiresReorderListDataBind { get; set; }
protected void ReorderList1_PreRender(object sender, EventArgs e)
{
if (RequiresReorderListDataBind)
{
ReorderList1.DataBind();
}
}
}
Tips
* Be sure to set the ContextType property of the EntityDataSource as described here.
* There are fewer issues when using a SqlDataSource as described here.
* Be sure to set the OrderBy property of your EntityDataSource. An example is “it.intOrder ASC”.