Wednesday, April 8, 2009

EQATEC .NET App Profiler

Someone posted a link to this .NET Profiler tool on Twitter (Twitter FTW!) - so I went and downloaded it and tried it. The tool is called EQATEC Profiler (you can download it here). So what is it?

The way it works it that it rebuild your dlls/exes so when you run it, the tool will be able to profile your .NET method calls and give you a report. Based on the report, you can see which method is the slowest, which one is being called the most number of times, etc. The hope is that based on that data then you can optimize your application to maximize the speed/performance.

When you downloaded the app, it comes with a sample WinForm app. You can use it to see how it works. When it finds the exe/Main() method, it will use it as a starting point when you run it.

My application is an ASP.NET MVC application - so it does not have a Main() method. How do I then profile it?

When you open the profiler, this the screen you will see. Click browse on the top right - and browse to your ASP.NET application bin directory. You can select a dll OR not select any file and just the folder (which will select all dlls in the bin folder). Hit "OK" then make sure all the dlls that you want to profile is checked. Hit the "Build" button on the lower right. Once it's done, you will see that there is a message in the message box saying that it does not find a Main() method. This is OK. Click "Open output folder" on the lower left, and copy all dlls in there back into your original bin folder. Then run your web application (Do NOT run it by hitting F5 - which will overwrite all the dlls from EQATEC. But run it by opening a browser and type "http://localhost:<port>/<appname>". At this point the EQATEC is already attached to your ASP.NET web app process and ready to profile it. So run through your web app (like doing a comprehensive walk-through) and before you are done, click "Run" tab on the EQATEC profiler then hit the "Take snapshot" button. If you are done at this point, you can close your browser. You can take a snapshot anytime you want and they are accumulative - so if you took 5 snapshots, the latest one will have the most comprehensive data out of the 5.

Now highlight the top most (or latest) snapshot you have taken, then click "View" button on the lower right of the "Run" tab. It displays the break-down of all of your method calls during the walk-through. The telemetry includes:

  • "Calls" - which is the number of times the method is being called during the walk-through session
  • "Avg (self)" - which is the average amount of time spent in that method alone in milliseconds.
  • "Total (self)" is the total amount of time spent in the method during the whole time spent in the walk-through.
  • "Total (full)" - the total amount of time spent in the method and all the other methods called by it.
  • "Avg (full)" is the average - "Total (full) / Calls".

From those data, we should be able to see what method takes the longest to execute and optimize it if necessary - OR - why is a particular method is being called hundred of times and accumulate cost over time and maybe we can put caching to reduce it, etc etc.

Overall, I like this tool as it provides me a way to pin-point the bottlenecks in my application and take action accordingly, instead of just guessing or doing a manual measurement - which is painful and tedious.
-- read more and comment ...

Monday, April 6, 2009

Silverlight 3.0, Astoria, LINQ to SQL (3): DataForm CRUD

In last few posts:here and here, I wrote about how to build/prepare the solution, projects, and some ground work in LINQ to SQL and connecting it to ADO.NET Data Service as well as reading data through a Silverlight application using Silverlight 3.0 DataForm.

In this post, I will continue in implementing the Update/Edit, Create/New, and Delete to complete our CRUD.

To do EDIT, it is actually very simple. First you will need to add event handlers. We will need two handlers, but both of them will be reusable for ADD. To add event handlers, all we need to do is start typing our DataForm name and select the event that we want to implement via intellisense and hit tab key twice. It will complete the line for you and create a stub for the event hendler method.

By the way, I changed our binding, so instead of going against Customer, it is against Shipper (both from Northwind db and should exist in the LINQ mapping from the auto-gen code).

static Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);

// new code
dfShipper.ItemEditEnding += new EventHandler<DataFormItemEditEndingEventArgs>(dfShipper_ItemEditEnding);
dfShipper.ItemEditEnded += new EventHandler<DataFormItemEditEndedEventArgs>(dfShipper_ItemEditEnded);
}

void dfShipper_ItemEditEnding(object sender, DataFormItemEditEndingEventArgs e)
{
northwindDSContext.UpdateObject(dfShipper.CurrentItem);
}

void dfShipper_ItemEditEnded(object sender, DataFormItemEditEndedEventArgs e)
{
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

The "BeginSaveChanges" and "EndSaveChanges" are there because our data connection is asynchronous.

For ADD, we need to change "dfShipper_ItemEditEnding" into this:
void dfShipper_ItemEditEnding(object sender, DataFormItemEditEndingEventArgs e)
{
if (dfShipper.IsAddingNew)
northwindDSContext.AddObject("Shippers", dfShipper.CurrentItem);
else
northwindDSContext.UpdateObject(dfShipper.CurrentItem);
}

For DELETE, we add this handler:
void dfShipper_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
{
northwindDSContext.DeleteObject(dfShipper.CurrentItem);
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

So our complete code should look like this:
public partial class MainPage : UserControl
{
static Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
dfShipper.ItemEditEnding += new EventHandler<DataFormItemEditEndingEventArgs>(dfShipper_ItemEditEnding);
dfShipper.ItemEditEnded += new EventHandler<DataFormItemEditEndedEventArgs>(dfShipper_ItemEditEnded);
dfShipper.DeletingItem += new EventHandler<System.ComponentModel.CancelEventArgs>(dfShipper_DeletingItem);
}

void dfShipper_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
{
northwindDSContext.DeleteObject(dfShipper.CurrentItem);
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

void dfShipper_ItemEditEnding(object sender, DataFormItemEditEndingEventArgs e)
{
if (dfShipper.IsAddingNew)
northwindDSContext.AddObject("Shippers", dfShipper.CurrentItem);
else
northwindDSContext.UpdateObject(dfShipper.CurrentItem);
}

void dfShipper_ItemEditEnded(object sender, DataFormItemEditEndedEventArgs e)
{
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var query = (DataServiceQuery<Shipper>)
from c in northwindDSContext.Shippers
orderby c.CompanyName
select c;

query.BeginExecute((asyncResult) =>
{
try
{
IEnumerable<Shipper> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Shipper> data = new ObservableCollection<Shipper>();
foreach (Shipper p in result)
{
data.Add(p);
}
dfShipper.DataContext = data;
dfShipper.ItemsSource = data;
});
}
catch (Exception ex)
{
throw ex;
}
}, null);
}
}

-- read more and comment ...

Wednesday, April 1, 2009

Silverlight 3.0, Astoria, LINQ to SQL (2): DataForm CRUD

In the last post, I wrote about how to build/prepare the solution, projects, and some ground work in LINQ to SQL and connecting it to ADO.NET Data Service. Now in this post, I am going to continue in building the Silverlight application using Silverlight 3.0 DataForm.

We will first add the service reference to our ADO.NET Service that we built from our Silverlight application. Then how to hook it up to our DataForm so it will display the first record by default.

1. Add a service reference to the ADO.NET Data Service ("NorthwindDataService.svc"). Run the solution with "NorthwindDataService.svc" as the Start page. Once it loads, copy the URL in the location bar and close the browser.

On the "Northwind.Silverlight" project, right click on "Service References" and click "Add Service Reference". Paste the URL you copy to clipboard into the "Address" field and click "Go". It should then populate the "Services" left pane. Hit OK.


2. Create the XAML. We need to add a DataForm to our XAML. Now, I do not have Expression Blend, so I did mine using Visual Studio. First of all, you need to add a reference to System.Windows.Controls.Data.DataForm. Your XAML should look like this:


<UserControl x:Class="Northwind.Silverlight.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="450">
<Grid x:Name="LayoutRoot" Background="White">
<df:DataForm x:Name="dfCustomer"></df:DataForm>
</Grid>
</UserControl>

3. Bind the data to the DataForm control. We need to bind the data to our dataform. There are multiple ways on how to go about doing this. In this post I bind the data as soon as the Silverlight application is loading. Or, you can create a button (or whatever event) to initiate the bind. Open "MainPage.xaml.cs" and create a handler method for the "Loaded" event. In this method, basically I will query the data using our data service reference and then bind the result to our dataform.

public partial class MainPage : UserControl
{
Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

var query = (DataServiceQuery<Customer>)
from c in northwindDSContext.Customers
orderby c.ContactName
select c;

query.BeginExecute((asyncResult) =>
{
try
{
IEnumerable<Customer> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Customer> data = new ObservableCollection<Customer>();
foreach (Customer p in result)
{
data.Add(p);
}
dfCustomer.DataContext = data;
dfCustomer.ItemsSource = data;
});
}
catch (Exception ex)
{
throw ex;
}
}, null);
}
}
Now, we do we need to do all that? It looks overly complicated for just doing a data binding. Why not just do a LINQ query and then bind the result directly? What's "BeginExecute" for? The answer to all that really boils down to because the call to the data service is being down asynchronously. Therefore, the callback is needed. If you look into the method signature of "BeginExecute", you will see that the first parameter is a callback function.

So let's break it down ... see the line where it does this?

query.BeginExecute((asyncResult) => { ... }, null);
That line basically is executing the query and then upon completion of the query and getting the result back (stuffed into the param "asyncResult"), run this generic method (stuff inside the curly braces).

So let's take a look the callback function (stuff inside the curly braces).

IEnumerable<Customer> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Customer> data = new ObservableCollection<Customer>();
foreach (Customer p in result)
{
data.Add(p);
}
dfCustomer.DataContext = data;
dfCustomer.ItemsSource = data;
});
The first line is the "closer" for the async call and it will return a result as an IEnumerable. Next we then using Dispatch.BeginInvoke (because we are transferring data over web service) get the data one by one and put them into an ObservableCollection. Then after that we bind our DataForm to the ObservableCollection.

So far so good? In the next post: Add, Edit, Delete!
-- read more and comment ...