Thursday, 11 November 2010

What are Microsoft’s real virtualization technologies?

OK, so something is either real or virtual, right? But behind the contradiction implied by this title, is a serious question. What exactly is virtualization?

Not only are virtualization technologies becoming more and more prevalent in all kinds of organizations, from small businesses through to global enterprises, but the term "virtualization" is also being used more and more, and sometimes to describe things that may not be virtualization as this term has commonly been understood. For example Microsoft, and other vendors, has applied the "virtualization" brush to a range of technologies, not all of which are really virtual! Or have they …

One definition of virtualization is: The capability to run an application or a computer in a virtual environment without affecting the components that already exist on that particular desktop or server.

This definition mostly works for desktop virtualization technologies, such as Virtual PC 2007 and Windows Virtual PC on Windows 7, VMware Workstation, and server virtualization technologies such as Microsoft Hyper-V and VMware Server, that enable several operating systems to co-exist on the same physical hardware, and in protected "bubbles" so that one OS does not affect any others.

However, as we shall see when we go through some of Microsoft's current "virtualization" technologies, the above definition doesn't always fully explain things.

Server virtualization is probably the least ambiguous technology area for Microsoft. Server virtualization enables multiple servers, such as domain controllers, DNS servers, and Web servers, to run as discrete virtual machines on the same physical computer host. Server virtualization enables the physical consolidation of multiple traditional servers to improve and maximize hardware utilization, and to consolidate workloads onto fewer enterprise class servers. Server virtualization is also about reducing costs, by using fewer physical computes, with reduced electrical costs for cooling and server power consumption.

But what about desktop virtualization? Desktop virtualization enables software applications, operating system, and hardware configuration to be packaged to run in a self-contained, virtual environment, enabling users to run multiple operating systems and applications on a single computer. With desktop virtualization technologies, such as Microsoft Virtual PC 2007 SP1 and Windows Virtual PC, including Windows 7's XP Mode, the virtual machine files are installed and executed on the local client desktop computer. In enterprise environments, desktop virtualization can be augmented with technologies such as MED-V that are used to deliver and manage client virtual machines over the network from central servers.

Isn't this clear and unambiguous? Yes, if you just include "traditional" definitions of desktop virtualization, but what about approaches such as using Microsoft's Remote Desktop Services (RDS) to deliver desktops and applications hosted on central servers, but presented on user's desktops as if they were running locally? This approach de-couples the user desktop from their physical workstation, but importantly there are not separate virtual machines on the server; rather every user is running a session on the server, and any applications they run must be compatible with the server OS. This approach is sometimes called presentation or session virtualization. And where shortcuts to remote applications are provided on user's desktops, this approach is sometimes also referred to as a type of application virtualization.

And then there is the whole area of Virtual Desktop Infrastructure (VDI) …. VDI is an alternative server-based desktop virtualization method that uses virtualization technologies enable users to run their own client desktops, but hosted on a central server. In Microsoft's VDI, multiple client operating systems run in virtual machines on a Hyper-V server; the server then remotely presents each desktop to the client computer, over RDS. By using virtual machines running on the server, every user can run their own desktop OS version (and not be limited to a server OS), and customize their own unique desktop settings.

So, there's more to virtualization than virtual machines; we've also got session-based virtualization and, perhaps most importantly, there's also the concept of de-coupling, where elements such as hardware, OS, applications, and user state and data, that in a traditional rich desktop environment would be all packaged together, are now potentially in their own separate spaces.

Does considering virtualization to be of different types help with some of the other "virtualization" technologies?

Let's consider user state virtualization. The term user state virtualization describes the ability to separate user data and settings from applications and operating systems, so that users can access their own personalized environment, no matter which device they are currently using. So, in this case, it is the de-coupling element of virtualization that is key – we are not talking about multiple concurrent user states here, and there are no virtual machines involved. Instead there's the ability to separate user state data from the applications and operating system so that users can move from environment to environment, or device to device, and automatically pick up their saved data, favorites, application settings and so on no matter where they are. In Windows environments, user state virtualization technologies include Folder Redirection, Offline Files, and Roaming User Profiles.

How about application virtualization? As a generic term application virtualization describes technologies that deliver applications across the network to end-users, without the need to install these applications on the user's desktop. There are two key methods for virtualizing applications; through specific application virtualization tools and through presentation virtualization.

Application virtualization tools typically package a software application so that it runs in a self-contained, virtual environment. This virtual environment contains all the information required to run the application on the client computer, without installing the software application locally. So, again the decoupling element of virtualization is important, as the application is not "installed" on the host operating system. In typical implementations, such as Microsoft's App-V, application code is downloaded on demand from a central server and executed on the local desktop computer. Although the application is running within a virtual OS environment, this is not a complete virtual machine; rather it provides virtualized interfaces to OS components such as the Windows Registry, and is a great approach for dealing with incompatibilities between applications; but is not useful for dealing with incompatibilities between an application and the host OS; this is because the application it does still make use of OS features, and must still meet the same platform and system requirements as a regular application; for example, if an application won't run under Windows 7 as a traditional installed application, it will still fail when run under App-V on a Windows 7 workstation.

Presentation virtualization typically describes technologies where the execution of application code takes place remotely from the end-user's interaction with the application interface. In Windows environments, presentation virtualization is used by applications delivered using RDS, such as traditional terminal services applications. Using technologies such as Microsoft's RemoteFX, such applications can appear to the end-user as if they are running locally, particularly if configured as a RemoteApp, where the individual application is launched from a regular application shortcut on the user desktop; in this way the user may be unaware that the application is actually a terminal into a remote session running on a server. So, again we've got de-coupling, and with sessions rather than virtual machines.

Looking at Microsoft as an example vendor, what we need to do is think of different types or classes of virtualization. The key feature in all types of virtualization is perhaps the de-coupling of elements such as hardware, OS, applications, and user state and data. Using this as a principle, we can see that the "traditional" approach, such as desktop and server virtualization, achieves this de-coupling by using distinct virtual machines. With user state virtualization, the de-coupling is achieved using remote central data storage; with application virtualization, the de-coupling can be from virtual application environments or through sessions in the case of remote applications and desktops. And implementations using VDI, can achieve de-coupling by using all these tools; virtual machines, centralized storage, and session-based technologies.

So, as long as we can think of the core virtualization concept as a separating or de-coupling approach, then there's no reason not to use the term for a wide range of approaches – and if this is case maybe all Microsoft's virtualizations are real ….


Thursday, 28 October 2010

Make Your Applications More Efficient with Multitasking

Multitasking is the ability to do more than one thing at the same time. As far as Windows applications are concerned, it is one of those concepts that is easy to describe but that, until recently, has been difficult to implement without getting embroiled in the threading mechanism provided by the .NET Framework and the operating system. A good implementation of multitasking achieves two objectives:

  1. It helps to ensure that an application remains responsive; a desktop application can wait and respond to user input on one thread while another thread performs any processing required.
  2. It assists in making the application scalable; if the application is deployed to a computer with a multicore processor, it can take advantage of the additional processing power to perform operations concurrently.

In the optimal scenario, an application running on a multicore processor performs as many concurrent tasks as there are processor cores available, keeping each of the cores busy. However, there are many issues you have to consider to implement concurrency, including the following:

  • How can you divide an application into a set of concurrent operations?
  • How can you arrange for a set of operations to execute concurrently, on multiple processors?
  • How can you ensure that you attempt to perform only as many concurrent operations as there are processors available?
  • If an operation is blocked (such as while it is waiting for I/O to complete), how can you detect this and arrange for the processor to run a different operation rather than sit idle?
  • How can you determine when one or more concurrent operations have completed?
  • How can you synchronize access to shared data to ensure that two or more concurrent operations do not inadvertently corrupt each other’s data?

To an application developer, the first question is a matter of application design. The remaining questions depend on the programmatic infrastructure—Microsoft provides the Task Parallel Library (TPL) to help address these issues.

Tasks, Threads, and the ThreadPool

The most important type in the TPL is the Task class in the System.Threading.Tasks namespace. The Task class is an abstraction of a concurrent operation. You create a Task object to run a block of code. You can instantiate multiple Task objects and start them running in parallel if sufficient processor cores are available.

Internally, the TPL implements tasks and schedules them for execution by using Thread objects and the ThreadPool class (in the System.Threading namespace). Multithreading and thread pools have been available with the .NET Framework since version 1.0, and you can use the Thread class directly in your code. However, the TPL provides an additional degree of abstraction that enables you to easily distinguish between the degree of parallelization in an application (the tasks) and the units of parallelization (the threads). On a single-processor computer, these items are usually the same. However, on a computer with multiple processors or with a multicore processor, they are different items. If you design a program based directly on threads, you will find that your application might not scale very well; the program will use the number of threads you explicitly create, and the operating system will schedule only that number of threads. This can lead to overloading and poor response time if the number of threads greatly exceeds the number of available processors, or inefficiency and poor throughput if the number of threads is less than the number of processors.

The TPL optimizes the number of threads required to implement a set of concurrent tasks and schedules them efficiently according to the number of available processors. The TPL uses a set of threads provided by the ThreadPool, and implements a queuing mechanism to distribute the workload across these threads. When a program creates a Task object, the task is added to a global queue. When a thread becomes available, the task is removed from the global queue and is executed by that thread. The ThreadPool implements a number of optimizations and uses a work-stealing algorithm to ensure that threads are scheduled efficiently.

Note: The ThreadPool was available in previous editions of the .NET Framework, but it has been enhanced significantly in the .NET Framework 4.0 to support Tasks.

You should be aware that the number of threads created by the .NET Framework to handle your tasks is not necessarily the same as the number of processors. Depending on the nature of the workload, one or more processors might be busy performing high-priority work for other applications and services. Consequently, the optimal number of threads for your application might be less than the number of processors in the machine. Alternatively, one or more threads in an application might be waiting for long-running memory access, I/O, or a network operation to complete, leaving the corresponding processors free. In this case, the optimal number of threads might be more than the number of available processors. The .NET Framework follows an iterative strategy, known as a hill-climbing algorithm, to dynamically determine the ideal number of threads for the current workload.

The important point is that all you have to do in your code is divide your application into tasks that can be run in parallel. The .NET Framework takes responsibility for creating the appropriate number of threads based on the processor architecture and workload of your computer, associating your tasks with these threads and arranging for them to be run efficiently. It does not matter if you divide your work up into too many tasks as the .NET Framework will only attempt to run as many concurrent threads as is practical; in fact, you are encouraged to "overpartition" your work as this will help to ensure that your application scales if you move it on to a computer that has more processors available.

Creating, Running, and Synchronizing Tasks

You can create Task objects by using the Task constructor. The Task constructor is overloaded, but all versions expect you to provide an Action delegate as a parameter. The task uses this delegate to run the method when it is scheduled. The following example creates a Task object that uses a delegate to run the method called doWork:

Task task = new Task(doWork);
...

private void doWork()
{
// The task runs this code when it is started
...
}

The default Action type references a method that takes no parameters. Other overloads of the Task constructor take an an Action<object> parameter representing a delegate that refers to a method that takes a single object parameter. These overloads enable you to pass data into the method run by the task. The following code shows an example:

Action<object> action;
action = doWorkWithObject;
object parameterData = ...;
Task task = new Task(action, parameterData);
...

private void doWorkWithObject(object o)
{
...
}

After you create a Task object, you can set it running by using the Start method, like this:

Task task = new Task(...);
task.Start();

The Start method is also overloaded, and you can optionally specify a TaskScheduler object to control the degree of concurrency and other scheduling options. It is recommended that you use the default TaskScheduler object built into the .NET Framework, although you can define your own custom TaskScheduler class if you really need to take more control over the way in which tasks are queued and scheduled.

You can use also a TaskFactory object to create and run a task in a single step. The constructor for the TaskFactory class enables you to specify a task scheduler and additional task creation options. The TaskFactory class provides the StartNew method to create and run a Task object. Like the Start method of the Task class, the StartNew method is overloaded, but all overloads expect a reference to a method that the task should run. Even if you do not currently specify any particular task creation options and you use the default task scheduler, you should still consider using a TaskFactory object; it ensures consistency, and you will have less code to modify to ensure that all tasks run in the same manner if you need to customize this process in the future. The Task class exposes the default TaskFactory used by the TPL through the static Factory property. You can use it like this:

Task task = Task.Factory.StartNew(doWork);

When the method run by a task completes, the task finishes, and the thread used to run the task can be recycled to execute another task.

A common requirement of applications that invoke operations in parallel is to synchronize tasks. The Task class provides the Wait method, which implements a simple task coordination method. It enables you to suspend execution of the current thread until the specified task completes, like this:

task.Wait(); // Wait at this point until task completes

You can wait for a set of tasks by using the static WaitAll, and WaitAny methods of the Task class. Both methods take a params array containing a set of Task objects. The WaitAll method waits until all specified tasks have completed, and WaitAny stops until at least one of the specified tasks has finished. You use them like this:

// Wait for both task1 and task2 to complete
Task.WaitAll(task, task2);

// Wait for either of task1 or task2 to complete
Task.WaitAny(task, task2);

In summary, the TPL makes it easy to build multitasking applications without having to be concerned about the details of multithreading. The TPL provides a large number of features beyond those described in this simple overview, such as the Parallel class that implements a concurrent version of some common programming constructs. The TPL also includes a number of collection classes in the System.Collections.Concurrent namespace that support synchronized concurrent access to data shared by multiple tasks.

Wednesday, 13 October 2010

Using a Transparent Background in Reporting Services

While watching the Japanese Formula 1 Grand Prix on Sunday, it struck me that TV sports broadcasters make a lot of use of transparent overlays when showing scores, results, times, statistics, or whatever. In the case of the Grand Prix, the driver rankings in the world championship were displayed on a semi-transparent overlay, behind which the live footage of the race circuit could be seen.

So, naturally I started to wonder how I could achieve a similar visual effect in a Reporting Services report, like this:

Product Sales Report

My first thought was to look at the BackgroundColor property of the Tablix data region and set the Transparency level. However, when I looked at the color picker control for the property, this is what I saw:

ColorProperties

Note that the Transparency control is disabled. It turns out you can only set a transparency level for gauges and charts in Reporting Services – not for shapes or data regions. So, I needed to find an alternative approach.

The answer I came up with was to create a semi-transparent .png graphic, and use it as the background image for the data region. I created this with PowerPoint, though of course you can use any graphics tool you like. I also used PowerPoint to find a suitable clipart image to use as the background for the report (on which the semi-transparent data region will be overlaid). In this case, I’m using the Adventure Works Cycles sample data, so a photo of a cyclist seems like a good choice.

PowerPoint

You can take one of two approaches when it comes to sizing the semi-transparent image – you can make an extremely small image and then set the BackgroundRepeat property of the data region to Repeat, or you can make it bigger than the data region is ever likely to be and set the BackgroundRepeat property to Clip (or Repeat – it won’t matter since the image will be bigger than the data region anyway!). I found that PowerPoint tends to add some whitespace to the edge of a shape when you save it as a .png image, which showed up when repeating the background image, so I went with a large background image. Of course, had I used a more comprehensive graphics tool,  could have easily avoided this issue and got away with repeating a smaller image.

To embed the images to the report, I added them to the Images folder in the Report Data pane in Report Designer.

ReportData

Then I set the BackgroundImage property of the tablix data region in which the report data is displayed, like so:

TablixProperties

I’ve also used the semi-transparent image as the background for the report title textbox, which appears above the tablix data region.

The next challenge was to apply the cyclist image to the background of the report, and ensure that the layout of the report overlays the data neatly. If you have a small data set with a known number of records(for example, in a “top 10 products” report), then this is relatively straightforward. However, for a dataset with an unknown size, the data region will be resized dynamically, and automatic pagination may break the report into multiple pages. In my case, I want to ensure that the report title appears on all pages, and that the table of data has a suitable space above and below it on all pages.

To accomplish this, I added a page header and footer to the report and put the report title in the header. This ensures that if the report is paginated, the table on the second (and all subsequent pages) doesn’t start right at the top of the page. Similarly, the report footer ensures that there’s always a space after the table – it never goes all the way to the bottom of the page. I set the BackgroundImage of the report to the cyclist picture (clipped so it doesn’t repeat), and I set the InteractiveSize property of the report so that when viewed in the browser, the report has a maximum size that will keep the tablix well within the background image area. This was made tricky by the fact that Report Designer does not show the background image of the report in design view, so I had to preview the report and assess the right size through trial and error.

Report Designer

Obviously, the report size is optimized for interactive viewing, and though you can set the PageSize property of the report to an appropriate size for any other renderers you plan to use, my experience is that using background images and contrived layouts in reports you intend to render to a different format can result in some pretty horrible looking exported reports. One solution I have used in the past for this is to create the version that’s tailored for online viewing, and include a link to an offline version that has more conventional formatting for printing or exporting.

You can download the sample report I created from here. You’ll also need SQL Server 2008 R2 with Reporting Services (you can get the free Express edition from here) and the AdventureWorksDW2008R2 sample database (which you can get from here).

del.icio.us Tags: ,

Saturday, 21 August 2010

Geocoding (and reverse-geocoding) with Bing Maps

In previous posts, I’ve explored how to use Bing Maps with SQL Server spatial data. In this post, I want to explore the Bing Maps control a little more. Specifically, I want to look at how to use the Bing Maps control to geocode a street address (that is, find the latitude and longitude coordinates of the address), and how to reverse-geocode a spatial location to find the corresponding street address.

The first step in building a Web page that uses the Bing Maps control to geocode an address, is to add a <div> tag to host the map control and use the page body’s onload event to call a JavaScript function that loads the map – like this:

<head>

<!-- add a reference to the Virtual Earth map control -->
<script type="text/javascript"
        src="
http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3">
</script>

<script type="text/javascript">
    function GetMap() {
        map = new VEMap('mapDiv');
        map.LoadMap();
    }
</script>

</head>
<body onload="GetMap()">

    <div id="mapDiv" style="position:relative; width:600px; height:400px;">
    </div>
</body>

Next, you need a textbox so that users can enter the address they want to geocode, a button they can click to geocode the address, and two textboxes to show the resulting latitude and longitude coordinates.

Address:<input id="txtAddress" type="text" style="width:340px" />
        <input style="width:60px" id="btnFind" type="button" value="Find" onclick="return btnFind_onclick()" />
Latitide:<input id="txtLat" type="text" style="width:400px" />
Longitide:<input id="txtLong" type="text" style="width:400px" />

Note the onclick property of the button control, this calls the function that uses Bing Maps to geocode the address. Here’s the code to do that:

function btnFind_onclick() {
    //Geocode the address to find the Lat/Long location
    map.Geocode(document.getElementById("txtAddress").value, onGeoCode, new VEGeocodeOptions())
}

Note that the code in the btnFind_onclick function calls the Geocode method of the map control, specifying the address to be geocoded, the name of the callback function to use to process the results (onGeoCode), and a VEGeocodeOptions object that ensures the user is shown a list of options when the address has multiple possible matches. The calback function looks like this:

function onGeoCode(layer, resultsArray, places, hasMore, veErrorMessage) {
    var findPlaceResults = null;

    // verify the search location was found
    if (places == null || places.length < 1) {
        alert("The address was not found");
    }
    else {
        // we've successfully geocoded the address, so add a pin
        findPlaceResults = places[0].LatLong;
        addPinToMap(findPlaceResults);
    }
}

The callback function is called when the geocode method returns, and assuming a location has been found the JavaScript calls the following addPinToMap function to display the results:

function addPinToMap(LatLon) {
    // clear all shapes and add a pin
    map.Clear()
    var pushpoint = new VEShape(VEShapeType.Pushpin, LatLon);
    map.AddShape(pushpoint);

    // center and zoom on the pin
    map.SetCenterAndZoom(LatLon, 13);

    // display the Lat and Long coordinates
    document.getElementById("txtLat").value = LatLon.Latitude;
    document.getElementById("txtLong").value = LatLon.Longitude;
}

This adds a pin to the map and centers and zooms to ensure it can be seen clearly. It then displays the latitude and longitude in the textboxes defined earlier.

We now have all the code required to geocode an address, but  what about the opposite? Ideally, we also want the user to be able to click a location on the map and reverse-geocode the point that was clicked to find the address.

Of course, the Bing Maps map control already responds to user clicks, so our application will use right-clicks to enable users to specify a location. We’ll do this by attaching an onclick event handler to the map control in the GetMap function (which you will recall is called when the page loads to display the map), and then checking for a right-click before reverse-geocoding the clicked location:

// added to the GetMap function
map.AttachEvent("onclick", map_click);

function map_click(e) {
    // check for right-click
    if (e.rightMouseButton) {
        var clickPnt = null;

        // some map views return pixel XY coordinates, some Lat Long
        // We need to convert XY to LatLong
        if (e.latLong) {
            clickPnt = e.latLong;
        } else {
            var clickPixel = new VEPixel(e.mapX, e.mapY);
            clickPnt = map.PixelToLatLong(clickPixel);
        }

        // add a pin to the map
        addPinToMap(clickPnt)

        //reverse-geocode the point the user clicked to find the street address
        map.FindLocations(clickPnt, onReverseGeoCode);
    }
}

This code finds the latitude and longitude of the clicked location (in some views, the map control uses X and Y pixel coordinates so we need to check for that), displays a pin on the map at the clicked location, and then uses the FindLocations method of the map control to find the address. A callback function named onReverseGeoCode is used to process the results:

function onReverseGeoCode(locations) {
    // verify the search location was found
    if (locations == null || locations.length < 1) {
        document.getElementById("txtAddress").value = "Address not found";
    }
    else {
        // we've successfully found the address, so update the Address textbox
        document.getElementById("txtAddress").value = locations[0].Name;
    }
}

The completed application looks like this:

BingGeocoder

You can try the page out for yourself here, and you can download the source code from here.

del.icio.us Tags:

Tuesday, 8 June 2010

Creating Multi-Sheet Workbooks with SQL Server 2008 R2 Reporting Services

One thing I’ve learned in over ten years of creating database and reporting solutions, is that no matter how dynamic and interactive you make online reports, no matter how to embed live reporting into the user interface of applications, and no matter how funky a dashboard you design; many executives don’t believe data is real unless it’s in a spreadsheet. That’s why one of the most used features of Reporting Services is the ability to render reports in Excel format.

However, I recently encountered a situation where Content Master hosts a Luminosity learning management system, and uses SQL Server Reporting Services to generate reports of student activity in Excel format. The number of students has grown substantially over time, and we hit an unforeseen problem – The Excel 2003 format that Reporting Services renders the reports in supports a maximum of 65,536 rows per worksheet, and the report (which shows students and all training they have completed) has grown to exceed this limit.

After some head scratching, I investigated enhanced new page-break support in SQL Server 2008 R2 and came up with a solution that works, and which can enhance the ability to create complex reports in Excel format for those pesky executives – so I thought I’d share it here.

Let’s imagine your executives want a report in Excel format that lists every customer, along with their contact details. If you have less than 65,537 customers, you could design a report that simply lists them in a worksheet, but if you have more customers than that (or you want to include headers, spaces, or other elements in your report that will use rows when rendered to Excel), then you’ll need a better solution. Ideally, you might want to create something like this – an Excel workbook with multiple worksheets, consisting of a generic “cover page” and a tab for each letter of the alphabet so that you can view customers by last name.

Workbook

You can download a copy of this workbook from here.

Each worksheet in the workbook lists customers with a last name that begins with the letter on the corresponding worksheet tab, as shown here:

Customers-A

To create this report, I used the AdventureWorks2008R2 sample database (which you can download from here) and the following Transact-SQL query:

SELECT Title, FirstName, LastName, AddressLine1, City, StateProvinceName, PostalCode, CountryRegionName
FROM Sales.vIndividualCustomer

The report includes a tablix data region that includes a details grouping (in which all fields are displayed) and a grouping based on the following expression (which returns the first character of the LastName field in upper-case):

=ucase(left(Fields!LastName.Value, 1))

I also added an image and a textbox to the report, and placed them above the tablix data region as shown here:

ReportDesign

To create the page breaks that generate the worksheets when rendered to Excel,  I’ve used some of the new page-break support in SQL Server 2008 R2. First of all, I’ve set the report’s InitialPageName property to Customer Addresses, as shown here:

ReportProperties

This property defines the default name for the first page of the report (or for all pages if no explicit page breaks with page names are defined). That’s why in the Excel workbook, the “cover page” has this name on its worksheet tab (if the InitialPageName property wasn’t set, the worksheet tab would show the the report name).

Next, I created a page break at the start of the tablix as shown here:

TablixProperties

This causes the data in the table to be displayed on a new page, effectively defining the “cover page” as “everything before this”.

Finally, I used the properties of the grouping I defined earlier to create a page break between each instance of the grouping, and apply a page name based on the same expression used to define the grouping. In other words, there will be a page for each first character of the LastName field, and the page name for this page will be the grouping character.

GroupProperties

You can download the complete solution from here. You’ll need to have an instance of SQL Server 2008 R2 with the AdventureWorks2008R2 database (the DataSet in the report assumes that this is in the default instance of SQL Server 2008 R2 on the local computer).

Exporting this report to Excel creates the desired multi-sheet workbook, with a tab for each initial character of the last name, and a “cover page”.

Hopefully, you can see from this article how easy it is to create multi-sheet workbook reports that will add value to your reporting solutions.