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 <ColumnName> <SortDirection>. 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.

<asp:ObjectDataSource ID="dsContracts" runat="server" 
SelectMethod="GetPerson"
TypeName="DataModel.DAL"
SortParameterName="sortExpression"
>
</asp:ObjectDataSource>

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();
}

Friday, June 12, 2009

Getting the SQL that was generated using LINQ to Entities

LINQ to Entities doesn’t have debugger support for getting the SQL that was generated for a query like LINQ to SQL does. However, you can easily get the generated SQL with a few lines of code. I have added some additional lines of code to make it more robust. I recommend adding the following method to your partial class that inherits from ObjectContext. This designer class is created by default when you create an ADO.NET Entity Data Model, but you don’t want to edit that. Instead you want to add a partial class with the same name and in the same namespace.  If you don’t want to put the code there you could modify it slightly to take the ObjectContext as a parameter and put it anywhere you want.

/// <summary>
/// For debugging only. Returns the SQL statement that is generated
/// by LINQ for an IQueryable object. This does NOT execute the query
/// </summary>
/// <param name="query">The IQueryable object</param>
/// <returns>The generated SQL as a string</returns>
public string GetGeneratedSQL(IQueryable query)
{
string sql = string.Empty;
bool weOpenedConnection = false;
try
{
if (Connection.State != ConnectionState.Open)
{
Connection.Open();
weOpenedConnection = true;
}
sql = (query as ObjectQuery).ToTraceString();
}
finally
{
if (weOpenedConnection)
{
Connection.Close();
}
}
return sql;

}
Here is how you use it.
using (ICA3Entities ctx = new ICA3Entities())
{
var query = from t in ctx.MyTable
select t;

string sql = ctx.GetGeneratedSQL(query);
}

For tools and more information on other options you may want to check out: http://www.scip.be/index.php?Page=ArticlesNET13#ToTraceString

Thursday, June 11, 2009

Controlling Opacity of a background color using CSS

If you want want to have a background color be semi-transparent (a color with opacity of less than 100%), Cascading Style Sheets (CSS) may be what you have been looking for.

FireFox and Internet Explorer implement the feature differently. However, both implements can co-exist in one CSS class so there is no need for fancy code to swap between the two depending on the browser.

.mySemitransparentBackground {
background-color: Gray;
filter:progid:DXImageTransform.Microsoft.alpha(opacity=70);
opacity: 0.7;
}

In this example, the background color is Gray, but could be any color. The line that starts filter is for Internet Explorer, and the line that starts with opacity is for FireFox. Notice that Internet Explorer takes the value 70 to specify that the color is 70% opaque, and FireFox use the decimal version which is 0.7 to specify the same 70% opacity. Note, that 100% means that your color will be solid / not transparent (no background will be seen through it). Be sure both values represent the same value, otherwise you will get different opacity for each browser.

NOTE: I have verified that the FireFox stuff also works for Safari on Windows. If anyone else confirms any other browsers or platforms, please let me know. Thx!

Wednesday, June 3, 2009

Just Geeks - 200th posting

Wow! I can hardly believe I am up to 200 postings.

It is amazing how much I have picked up from programming on a daily basis. It seems hard to imagine that someone could get bored programming.

I sincerely hope this blog helps many people. According to Google Analytics I get about 550 people visiting my blog a day. I guess I must be doing something right, so I think I will keep on writing. If nothing else I use my own blog to help document what I learn.

Thanks for all your support.

Brent V

Downloading Binary content using ASP.NET and LINQ

Here’s the scenario. You have some binary data that is in a database. You want to use LINQ to SQL or LINQ to Entities to pull the data out of the database and return the user for viewing when they hit a particular url.

Let’s pretend you have a page called Download.aspx and you want to use the primary key in the url to specify what file should be downloaded when the user hits this page.

For example, if the url is http://myapp/Download.aspx?AttachmentID=123 where 123 is the primary key in a table called Attachment that has our binary data.

Below is an LINQ to Entities example, but a LINQ to SQL would be virtually identical.

protected void Page_Load(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(Request.QueryString["AttachmentID"]))
{
int attachmentID = Convert.ToInt32(Request.QueryString["AttachmentID"]);

using (MyEntities ctx = new MyEntities())
{
var attachment = ctx.Attachment.FirstOrDefault<MyAttachment>(a => a.ID == attachmentID);
if (attachment != null)
{

byte[] binaryData = attachment.DataField;
Response.Clear();
Response.AddHeader("content-disposition", string.Format("attachement; filename=\"{0}\"", attachment.FileName));
Response.ContentType = attachment.MimeType;
Response.BinaryWrite(binaryData);
Response.End();

}

}
}
}

Monday, June 1, 2009

Microsoft Chart Controls from code

Below are two methods that you can call from your console app or Win Forms app to create a chart using MS Chart Controls. You will needs Visual Studio 2008 SP1 (.NET 3.5.1 SP1). You will need to download the appropriate Microsoft Charting Control files. See here for a list of files to download, and here for a good article on what MS Charting Controls have to offer.

This example assumes you don’t want to use the visual designer in Visual Studio for some reason and that you want to control the entire lifecycle of the Windows Control.

The first example is called DatabaseLikeTest and is meant to simulate querying a database using LINQ to SQL, but the example could be applied to any kind of object that holds data like a DataSet or direct database query.

The second example is called HardCodedLikeTest and is just shows you how to do a very basic chart.

In both cases, the output is a PING file, though other formats could be output just by changing the ChartImageFormat of the SaveImage method.

using System.Windows.Forms.DataVisualization.Charting;

...

public class Thing
{
public int MyNum { get; set; }
public string MyDescription { get; set; }
}

private void DatabaseLikeTest()
{
// this could be a database
List<Thing> things = new List<Thing>();
things.Add(new Thing { MyDescription = "My Data 1", MyNum = 14 });
things.Add(new Thing { MyDescription = "My Data 2", MyNum = 34 });
things.Add(new Thing { MyDescription = "My Data 3", MyNum = 42 });
things.Add(new Thing { MyDescription = "My Data 4", MyNum = 18 });
things.Add(new Thing { MyDescription = "My Data 5", MyNum = 24 });
things.Add(new Thing { MyDescription = "Bogus Value", MyNum = 100 });

// this could be a linq to sql query here
var filteredThings = things.Where(p => p.MyNum < 50);

Chart myChart = new Chart();
myChart.Size = new Size(500, 500);

ChartArea myChartArea = new ChartArea();
myChart.ChartAreas.Add(myChartArea);

Series series = new Series("mySeries");

foreach (var thing in filteredThings)
{
series.Points.AddXY(thing.MyDescription, thing.MyNum);
}

myChart.Series.Add(series);

series.Points[2].Label = "High Data";
series.Points[2].Color = Color.Green;

Font font = new Font("Arial", 24, FontStyle.Bold);
Title title = new Title();
title.Text = "Test Chart";
title.Font = font;

myChart.Titles.Add(title);

myChart.SaveImage(@"C:\temp\test2.png", ChartImageFormat.Png);
}

private void HardCodedTest()
{
Chart myChart = new Chart();
myChart.Size = new Size(500, 500);

ChartArea myChartArea = new ChartArea();
myChart.ChartAreas.Add(myChartArea);

Series series = new Series("mySeries");
series.Points.Add(14);
series.Points.Add(34);
series.Points.Add(42);
series.Points.Add(18);
series.Points.Add(24);
myChart.Series.Add(series);

series.Points[0].AxisLabel = "My Data 1";
series.Points[1].AxisLabel = "My Data 2";
series.Points[2].AxisLabel = "My Data 3";
series.Points[3].AxisLabel = "My Data 4";
series.Points[4].AxisLabel = "My Data 5";

series.Points[2].Label = "High Data";
series.Points[2].Color = Color.Green;

Font font = new Font("Arial", 24, FontStyle.Bold);
Title title = new Title();
title.Text = "Test Chart";
title.Font = font;

myChart.Titles.Add(title);

myChart.SaveImage(@"C:\temp\test.png", ChartImageFormat.Png);
}

The chart will look something like this:

image


Truncating Log File in SQL Server

Sometimes a transaction log file in SQL Server gets too large and needs to be shrunk.

To see how much free space you will be able to reclaim, run the following query before and / or after you shrink the log file.

SELECT name ,size/128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS int)/128.0 AS AvailableSpaceInMB
FROM sys.database_files;

Below is a quick T-SQL snippet of code that you can run to truncate (shrink) you SQL Server transaction log file.The snippet will shrink you database to 1MB. You will need to change MyDB and MyDB_Log to match your database.


USE MyDB
GO
DBCC SHRINKFILE(MyDB_Log, 1)
BACKUP LOG MyDB WITH TRUNCATE_ONLY
DBCC SHRINKFILE(MyDB_Log, 1)
GO

I believe the log file is usually named using the convention MyDB_Log, but if that does work, or you want to check for sure, just get properties on your database by right-clicking it in SQL Server Management Studio, and going to the Files page. Look at the Logical name of the log file. That is what you want to use.

SQL Server needs some free space just for daily operations. So, don’t be surprised if your log file grows a little after you shrink it. Though, typically, it will be a small amount.
WARNING: With any of this, you will lose the log of transactions since you have deleted the transaction log.

Troubleshooting

The above didn’t error, but it didn’t reduce the size of the log file. Here are some things to check.

  • Is there a backup job that is currently running? If so, wait for it to stop, or stop the backup job.
  • Is there a long transaction that is currently running? If so, wait for it to stop, or kill the transaction.
  • Is there an SSIS package or other job running that could potentially lock the database you are trying to shrink? If so, wait for it to stop or kill it.
  • If the log file doesn’t shrink (usually due to a transaction running), you may need wait for the transaction to finish, or to put the database in single user mode (under Properties | Options). Then run the DBCC SHRINKFILE command. 
  • If all else fails and you get desperate, you can detach your database (you may need to put it in single user mode first especially if you are out of disk space), then manually go to the file system and manually delete the log file. Then attach the database again; a new log file will be created.

You may also find other entry on this topic useful. For more information on the topic, I recommend MSDN docs. They are actually quite helpful on this topic. I also, recommend this blog posting. It is where I started.