Monday, 21 May 2012

What is Windows Azure Blob Storage


What is Windows Azure Blob Storage

Windows Azure Blob storage is a service for storing large amounts of unstructured data that can be accessed from anywhere in the world via HTTP or HTTPS. A single blob can be hundreds of gigabytes in size, and a single storage account can contain up to 100TB of blobs. Common uses of Blob storage include:
  • Serving images or documents directly to a browser
  • Storing files for distributed access
  • Streaming video and audio
  • Performing secure backup and disaster recovery
  • Storing data for analysis by an on-premise or Windows Azure-hosted service
You can use Blob storage to expose data publicly to the world or privately for internal application storage.

Concepts

The Blob service contains the following components:
Blob1
  • Storage Account: All access to Windows Azure Storage is done through a storage account. This is the highest level of the namespace for accessing blobs. An account can contain an unlimited number of containers, as long as their total size is under 100TB.
  • Container: A container provides a grouping of a set of blobs. All blobs must be in a container. An account can contain an unlimited number of containers. A container can store an unlimited number of blobs.
  • Blob: A file of any type and size. There are two types of blobs that can be stored in Windows Azure Storage: block and page blobs. Most files are block blobs. A single block blob can be up to 200GB in size. This tutorial uses block blobs. Page blobs, another blob type, can be up to 1TB in size, and are more efficient when ranges of bytes in a file are modified frequently. For more information about blobs, seeUnderstanding Block Blobs and Page Blobs.
  • URL format: Blobs are addressable using the following URL format:
    http://<storage account>.blob.core.windows.net/<container>/<blob>

    The following URL could be used to address one of the blobs in the diagram above:
    http://sally.blob.core.windows.net/movies/MOV1.AVI

Create a Windows Azure Storage Account

You need a Windows Azure storage account to use the blob storage service. You can manually create a storage account by following the below steps (you can also programmatically create a storage account using the REST API):
  1. In the navigation pane, click Hosted Services, Storage Accounts & CDN.
  2. At the top of the navigation pane, click Storage Accounts.
  3. On the ribbon, in the Storage group, click New Storage Account.
    Blob2

    The Create a New Storage Account dialog box will then open:
    Blob3
  4. In Choose a Subscription, select the account subscription that the storage account will be used with.
  5. In Enter a URL, type a subdomain name to use in the URI for the storage account. The entry can contain from 3-24 lowercase letters and numbers. This value becomes the host name within the URI that is used to address Blob, Queue, or Table resources for the subscription.
  6. Choose a region or an affinity group in which to locate the storage. If you will be using storage from your Windows Azure application, select the same region where you will deploy your application.
  7. Click OK.
  8. Click the View button in the right-hand column below to display and save the Primary access key for the storage account. You will need this in subsequent steps to access storage.
    Blob4

Setup a Windows Azure Storage Connection String

The Windows Azure .NET storage API supports using a storage connection string to configure endpoints and credentials for accessing storage services. You can put your storage connection string in a configuration file, rather than hard-coding it in code. In this guide, you will store your connection string using the Windows Azure service configuration system. This service configuration mechanism is unique to Windows Azure projects and enables you to dynamically change configuration settings from the Windows Azure Management Portal without redeploying your application.
To configure your connection string in the Windows Azure service configuration:
  1. Within the Solution Explorer of Visual Studio, in the Roles folder of your Windows Azure Deployment Project, right-click your web role or worker role and click Properties.
    Blob5
  2. Click the Settings tab and press the Add Setting button.
    Blob6
    A new Setting1 entry will then show up in the settings grid.
  3. In the Type drop-down of the new Setting1 entry, choose Connection String.
    Blob7
  4. Click the ... button at the right end of the Setting1 entry. The Storage Account Connection String dialog will open.
  5. Choose whether you want to target the storage emulator (the Windows Azure storage simulated on your local machine) or an actual storage account in the cloud. The code in this guide works with either option. Enter the Primary Access Key value copied from the earlier step in this tutorial if you wish to store blob data in the storage account we created earlier on Windows Azure.
    Blob8
  6. Change the entry Name from Setting1 to a "friendlier" name like StorageConnectionString. You will reference this connectionstring later in the code in this guide.
    Blob9
You are now ready to perform the How To's in this guide.

How to Programmatically access Blob Storage Using .NET

Add the following code namespace declarations to the top of any C# file in which you wish to programmatically access Windows Azure Storage:
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
You can use the CloudStorageAccount type and RoleEnvironment type to retrieve your storage connection-string and storage account information from the Windows Azure service configuration:
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));
CloudBlobClient type allows you to retrieve objects that represent containers and blobs stored within the Blob Storage Service. The following code creates a CloudBlobClient object using the storage account object we retrieved above:
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

How to Create a Container

All storage blobs reside in a container. You can use a CloudBlobClient object to get a reference to the container you want to use. You can create the container if it doesn't exist:
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));

// Create the blob client 
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve a reference to a container 
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");

// Create the container if it doesn't already exist
container.CreateIfNotExist();
By default, the new container is private, so you must specify your storage account key (as you did above) to download blobs from this container. If you want to make the files within the container available to everyone, you can set the container to be public using the following code:
container.SetPermissions(
   new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob }); 
Anyone on the Internet can see blobs in a public container, but you can modify or delete them only if you have the appropriate access key.

How to Upload a Blob into a Container

To upload a file to a blob, get a container reference and use it to get a blob reference. Once you have a blob reference, you can upload any stream of data to it by calling the UploadFromStream method on the blob reference. This operation will create the blob if it didn't exist, or overwrite it if it did. The below code sample shows this, and assumes that the container was already created.
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));

// Create the blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve reference to a previously created container
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");

// Retrieve reference to a blob named "myblob"
CloudBlob blob = container.GetBlobReference("myblob");

// Create or overwrite the "myblob" blob with contents from a local file
using (var fileStream = System.IO.File.OpenRead(@"path\myfile"))
{
    blob.UploadFromStream(fileStream);
} 

How to List the Blobs in a Container

To list the blobs in a container, first get a container reference. You can then use the container's ListBlobs method to retrieve the blobs within it. The following code demonstrates how to retrieve and output the Uri of each blob in a container:
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));

// Create the blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve reference to a previously created container
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");

// Loop over blobs within the container and output the URI to each of them
foreach (var blobItem in container.ListBlobs())
{
    Console.WriteLine(blobItem.Uri);
} 
The blob service has the concept of directories within containers, as well. This is so that you can organize your blobs in a more folder-like structure. For example, you could have a container named 'photos', in which you might upload blobs named 'rootphoto1', '2010/photo1', '2010/photo2', and '2011/photo1'. This would virtually create the directories '2010' and '2011' within the 'photos' container. When you call ListBlobs on the 'photos' container, the collection returned will contain CloudBlobDirectory and CloudBlob objects representing the directories and blobs contained at the top level. In this case, directories '2010' and '2011', as well as photo 'rootphoto1' would be returned. Optionally, you can pass in a new BlobRequestOptions class with UseFlatBlobListing set to true. This would result in every blob being returned, regardless of directory. For more information, seeCloudBlobContainer.ListBlobs on MSDN.

How to Download Blobs

To download blobs, first retrieve a blob reference. The following example uses the DownloadToStream method to transfer the blob contents to a stream object that you can then persist to a local file. You could also call the blob's DownloadToFile, DownloadByteArray, or DownloadText methods.
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));

// Create the blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve reference to a previously created container
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");

// Retrieve reference to a blob named "myblob"
CloudBlob blob = container.GetBlobReference("myblob");

// Save blob contents to disk
using (var fileStream = System.IO.File.OpenWrite(@"path\myfile"))
{
    blob.DownloadToStream(fileStream);
} 

How to Delete Blobs

Finally, to delete a blob, get a blob reference, and then call the Delete method on it.
// Retrieve storage account from connection-string
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString"));

// Create the blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve reference to a previously created container
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");

// Retrieve reference to a blob named "myblob"
CloudBlob blob = container.GetBlobReference("myblob");

// Delete the blob
blob.Delete(); 

Tuesday, 1 May 2012

Using MERGE statement in SQL Server 2008

‘MERGE’ statement is a new feature in SQL Server 2008. It can be used to perform insert, update and delete operation on a destination table simultaneously based on the results of a join with a source table. Well, it sounds like a bit confusing, but let's see an example on how it can help us.
Assume we have following two tables.
  • STUDENT_A
  • STUDENT_B
Both table are identical in structure (Structure does not need to be identical).
STUDENT_A
img_scr_001

STUDENT_B
img_scr_002

And we have to update the ‘STUDENT_A’ with the details at ‘STUDENT_B’. We need to compare and if student ID’s are matched, ‘A’ table should be updated with the ‘B’ table. And if the ID’s in ‘B’ Table are new then we have to insert those to the ‘A’ table.
img_scr_003

So using the ‘MERGE’ statement we can achieve this in one execution.
Syntax:
MERGE  <Target> [AS T]
USING    <Source> [AS S]
ON <Condition>
[WHEN MATCHED THEN <Execution>]
[WHEN NOT MATCHED BY TARGET <Execution>]
[WHEN NOT MATCHED BY SOURCE <Execution>]

And to do the above operation use the following code:

MERGE STUDENT_A AS T
USING STUDENT_B AS S
ON T.ID = S.ID
WHEN MATCHED THEN UPDATE SET T.AGE = S.AGE
WHEN NOT MATCHED THEN INSERT (ID, FNAME, LNAME, AGE) VALUES(S.ID,S.FNAME,S.LNAME,S.AGE);


**Please note that semicolon ‘;’ is mandatory.

So after executing the above code, and if you inspect the Table ‘A’, you can see that it’s updated the way we wanted.

img_scr_005




And also you can use additional rules other than your condition. To illustrate that, first we insert a record to both the tables.

insert into STUDENT_A 
select 10, 'John','Doe',30

insert into STUDENT_B 
select 10, 'John','Doe',30


And using the following code you can remove the record with matches the condition and have the value 10.

MERGE STUDENT_A AS T
USING STUDENT_B AS S
ON T.ID = S.ID
WHEN MATCHED and S.ID < 5 THEN UPDATE SET T.AGE = S.AGE
WHEN MATCHED and S.ID = 10 THEN DELETE
WHEN NOT MATCHED BY TARGET THEN INSERT (ID, FNAME, LNAME, AGE) VALUES(S.ID,S.FNAME,S.LNAME,S.AGE);


And if you inspect the table A, you can see that it has the same following results:

img_scr_005

Locks and Duration of Transactions in MS SQL Server

It is a common argument which I hear among developers these days, regarding SQL locks. Some say that the ‘locks are held for the duration of the entire transaction’. But others debate that ‘locks will be only held for the duration of the statement execution’. But who is correct ?
Well both parties are correct up to a certain point. Actually lock durations are depend on the Isolation Levels.
As mentioned in the SQL-99 Standards, there are 4 Transaction Isolation Levels
  • Read Committed (Default)
  • Read Uncommitted
  • Repeatable Read
  • Serializable
SQL Server** provides following two additional isolation levels (** SQL Server 2005 & Upwards)
  • Snapshot
  • Read Committed Snapshot
There are several concurrency issues which can occur in a DBMS when multiple users try to access the same data. Each isolation level protects against a specific concurrency problem.
  • Lost Update
  • Dirty Read
  • Non-Repeatable Read
  • Phantom Reads

Lost Update – This can take place in two ways. First scenario: it can take place when data that has been updated by one transaction (Transaction A), overwritten by another transaction (Transaction B), before the Transaction A commits or rolls back. (But this type of lost update can never occur in SQL Server** under any transaction isolation level)
img_screen_02
The second scenario is when one transaction (Transaction A) reads a record and retrieve the value into a local variable and that same record will be updated by another transaction (Transaction B). And later Transaction A will update the record using the value in the local variable. In this scenario the update done by Transaction B can be considered as a ‘Lost Update’.
img_screen_04

Dirty Read – This is when the data which is changed by one transaction (Uncommitted) is accessed by a different transaction. All isolation levels except for the ‘Read Uncommitted’ are protected against ‘Dirty Reads’.
img_screen_05

Non Repeatable Read – This is when a specific set of data which is accessed more than once in one transaction (Transaction A) and between these accesses, it’s being updated or deleted by another transaction (Transaction B). The repeatable read, serializable, and snapshot isolation levels protect a transaction from non-repeatable reads.
img_screen_03

Phantom Read – This is when two queries in the same transaction, against the same table, use the same ‘WHERE’ clause, and the query executed last returns more rows than the first one. Only the serializable and snapshot isolation levels protect a transaction from phantom reads.
img_screen_06

In order to solve the above mentioned concurrency issues, SQL Server uses the following type of locks.
  • Shared or S-locks - Shared locks are sometimes referred to as read locks. There can be several shared locks on any resource (such as a row or a page) at any one time. Shared locks are compatible with other shared locks.
  • Exclusive or X-locks - Exclusive locks are also referred to as write locks. Only one exclusive lock can exist on a resource at any time. Exclusive locks are not compatible with other locks, including shared locks.
  • Update or U-locks - Update locks can be viewed as a combination of shared and exclusive locks. An update lock is used to lock rows when they are selected for update, before they are actually updated. Update locks are compatible with shared locks, but not with other update locks.
Please refer to the following link to get more information regarding lock types. http://msdn.microsoft.com/en-us/library/ms175519.aspx
As I have mentioned earlier, the type of lock which the SQL server will be acquired depends on the active transactions isolation level. I will briefly describe each isolation level a bit further.
Read Committed Isolation Level – This is the default isolation level for new connections in SQL Server. This makes sure that dirty reads do not occur in your transactions. If the connection uses this isolation level, and if it encounters a dirty row while executing a DML statement, it’ll wait until the transaction which owns that row has been committed or rolled back, before continuing execution further ahead.
img_screen_07

Read Uncommitted Isolation level - Though this is not highly recommended by experts, it's better to consider about it too. It may result in a 'dirty read', but when correctly used it could provide great performance benefits.
You should consider using this isolation level only in routines where the issue of dirty reads is not a problem. Such routines usually return information that is not directly used as a basis for decisions. A typical example where dirty reads might be allowed is for queries that return data that are only used in lists in the application (such as a list of customers) or if the database is only used for read operations.
The read uncommitted isolation level is by far the best isolation level to use for performance, as it does not wait for other connections to complete their transactions when it wants to read data that these transactions have modified. In the read uncommitted isolation level, shared locks are not acquired for read operations; this is what makes dirty reads possible. This fact also reduces the work and memory required by the SQL Server lock manager. Because shared locks are not acquired, it is no problem to read resources locked by exclusive locks. However, while a query is executing in the read uncommitted isolation level, another type of lock called a ‘schema stability lock’ (Sch-S) is acquired to prevent Data Definition Language (DDL) statements from changing the table structure. Below is an example of the behavior of this isolation level.
img_screen_08

Repeatable Read Isolation Level - In this isolation level, it guarantees that dirty reads do not happen in your transaction. Also it makes sure that if you execute/issue two DML statements against the same table with the same where clause, both queries will return the same results. But this isolation level will protect against updates and deletes of earlier accessed rows, but not the inserts, which is known as ‘Phantom’ rows concurrency problem. Note that phantom rows might also occur if you use aggregate functions, although it is not as easy to detect.
img_screen_09

Serializable Isolation Level – This guarantees that none of the aforesaid concurrency issues can occur. It is very much similar to the ‘repeatable read isolation level’ except that this prevents the ‘phantom read’ also. But use of this isolation level increases the risk of having more blocked transactions and deadlocks compared to ‘Repeat Read’. However it will guarantee that if you issue two DML statements against the same table with the same WHERE clause, both of them will return exactly the same results, including same number of row count. To protect the transaction from inserts, SQL Server will need to lock a range of an index over a column that is included in the WHERE clause with shared locks. If such an index does not exist, SQL Server will need to lock the entire table.

Snapshot Isolation Level – In addition to the SQL’s standard isolation levels, SQL 2005 introduced ‘Snapshot Isolation Level’. This will protect against all the above mentioned concurrency issues, like the ‘Serializable Isolation Level’. But the main difference of this is, that it does not achieve this by preventing access to rows by other transaction. Only by storing versions of rows while the transaction is active as well as tracking when a specific row was inserted.
To illustrate this I will be using a test database. It’s name is ‘SampleDB’. First you have to enable the ‘Snapshot Isolation Level’ prior using it
alter database SampleDB set allow_snapshot_isolation on;
alter database SampleDB set read_committed_snapshot off;

Now we’ll create a sample table and insert few records.

create table SampleIsolaion(
    id int,
    name varchar(20),
    remarks varchar(20) default ''
)

insert into SampleIsolaion (id,name,remarks)
select 1, 'Value A', 'Def' union
select 2, 'Value B', 'Def'



img_screen_10





Read Committed Snapshot Isolation Level – This can be considered as a new implementation of the ‘Read Committed’ isolation level. When this option is set, this provides statement level read consistency and we will see this using some examples in the post. Using this option, the reads do not take any page or row locks (only SCH-s: Schema Stability locks) and read the version of the data using row versioning by reading the data from tempdb. This option is set at the database level using the ALTER DATABASE command

I will illustrate the use of this isolation level with a sample. First enable the required isolation level.

alter database SampleDB set read_committed_snapshot on;
alter database SampleDB set allow_snapshot_isolation on;


Now lets create a table and populate it with few sample data.

create table sample_table(
    id int,
    descr varchar(20),
    remarks varchar(20)
)

insert into sample_table
select 1,'Val A','Def' union
select 2,'Val B','Def' 

Now open two query windows in SQL Server Management Studio.

--Window 1
begin tran
    update sample_table set descr = 'Val P', remarks = 'Window 1' where id = 1



Without committing execute the following in the second window


--Window 2
begin tran
    set transaction isolation level read committed    
    select * from sample_table
    

And you can see, even without committing, it’ll read from the older values, from the row versions which were created in the tempdb. If it was only the ‘Read Commited’ isolation level without the ‘Read Committed Snapshot’ option turned on, this select statement would have been locked.

Using SQL Server Table Variables to Eliminate the Need for Cursors

Today, I encountered a tricky problem about performance.
There is one table which is used to store both kinds of members including the Company Member(with RECORD_TYPE 'C') and the Individual Member(with RECORD_TYPE 'I').
When it comes to an individual one, it has a specific field 'COMPANY_ID', which stores the MB_ID as its company ID when it belongs to a company. The field can also be NULL. By comparison, a company one has a specific field 'MB_COUNT', which stores the total count of individual members whose COMPANY_ID equals the company member's MB_ID.
Now, we need to write a procedure to calculate the MB_COUNT of each Company Records. It will show how many individuals the company has.
The below pic show the original data with 'MB_COUNT' NULL:
Using Sql Server Table Variables to Eliminate the Need for Cursors The below picture shows the result data with 'MB_COUNT' calculated:
res02.jpg

Preparation

You can create your own testing database, then run the SQL-Scripts stored in the ZIP file (or run the below 1-5 scripts manually) to create the testing table and dynamic testing data.
  1. Create your testing DB:
    IF EXISTS(SELECT 1 FROM MASTER..SYSDATABASES WHERE NAME='TEST_DB1')
     DROP DATABASE TEST_DB1
    GO
    CREATE DATABASE TEST_DB1
    GO
    USE TEST_DB1
    GO
  2. Drop Table MEMBERS if it exists:
    IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = _
     object_id(N'MEMBERS') and OBJECTPROPERTY(id, N'IsUserTable') = 1)  
     DROP TABLE MEMBERS
  3. Create Table MEMBERS:
    CREATE TABLE MEMBERS(
     MB_ID INT PRIMARY KEY,
     MEMBER_NAME NVARCHAR(100),
     RECORD_TYPE CHAR(1),
     COMPANY_ID INT,
     MB_COUNT INT
    )
  4. Insert Company Data with MB_ID from 1001 to 6000:
    DECLARE @num INT
    SET @num = 1
    
    WHILE(@num <= 5000)
    BEGIN
     INSERT INTO MEMBERS(MB_ID, MEMBER_NAME, RECORD_TYPE, _
      COMPANY_ID, MB_COUNT)VALUES(1000+@num, 'Company' + _
      convert(varchar, @num), 'C', NULL, NULL);
     SET @num = @num + 1
    END
  5. Insert Individual Data with MB_ID from 10001 to 40000, utilize RAND() method to create random COMPANYID from 1001 to 6000.:
    SET @num = 1
    
    WHILE(@num <= 30000)
    BEGIN
     INSERT INTO MEMBERS(MB_ID, MEMBER_NAME, RECORD_TYPE, _
      COMPANY_ID, MB_COUNT)VALUES(10000+@num, 'Individual' + _
      convert(varchar, @num), 'I', 1001 + FLOOR(RAND()*5000), NULL);
     SET @num = @num + 1
    END

Solution

Actually, I have two totally different ways to achieve the requirement. Use 'cursor' or 'table variable'.
SOLUTION ONE--Use 'cursor' to loop records the whole table when the record stands for company:
DECLARE @ID INT,
  @MemberCount INT

DECLARE CUR1 CURSOR FOR
 SELECT MB_ID FROM MEMBERS 
 WHERE RECORD_TYPE = 'C'

OPEN CUR1
     FETCH CUR1 INTO @ID
WHILE @@FETCH_STATUS = 0
BEGIN
 SELECT @MemberCount = COUNT(1)FROM MEMBERS 
      WHERE MEMBERS.COMPANY_ID = @ID
 UPDATE MEMBERS
      SET MB_COUNT=@MemberCount
      WHERE MEMBERS.MB_ID = @ID
 
 FETCH NEXT FROM CUR1 INTO @ID
END
Close CUR1
DEALLOCATE CUR1
Solution One's Result:
solution01.jpg Initialize the MB_COUNT, use it to initialize the MB_COUNT to NULL.
UPDATE MEMBERS SET MB_COUNT = NULL
SOLUTION TWO--Use 'table variable' to solve this issue:
DECLARE @TEMP_TABLE TABLE (COMPANY_MB_ID INT, MB_COUNT INT)

INSERT INTO 
 @TEMP_TABLE 
SELECT 
 COMPANY_ID, count(MB_ID) AS MB_COUNT FROM MEMBERS
WHERE 
 RECORD_TYPE = 'I' AND COMPANY_ID IS NOT NULL
GROUP BY COMPANY_ID
ORDER BY COMPANY_ID ASC

UPDATE 
 MEMBERS
SET 
 MB_COUNT=(SELECT T.MB_COUNT FROM @TEMP_TABLE T _
  WHERE MEMBERS.MB_ID = T.COMPANY_MB_ID)
WHERE 
 MEMBERS.MB_ID IN (SELECT T2.COMPANY_MB_ID FROM @TEMP_TABLE T2)
Solution Two's Result:
solution02.jpg

Conclusion

Now we come to a conclusion: Don't ever use cursors in your SQL statement unless you are DBAs. For cursors will lock tables and they would affect the performance of the whole system. Beginners may feel comfortable with using cursors without concerning its poor performance. Let me say it again: DON'T use cursors. Try to use Table Variables!
Remember: Almost everything that you may first envision as requiring cursors to achieve can actually be done using the new SQL Server TABLE type. Let’s discard cursors and meet the challenge!

Expander Control is a Collapsable panel

Step 1
Start Microsoft Visual Web Developer 2010 Express, then Select File then New Project... Select "Visual Basic" then "Silverlight Application" from Templates, select a Location if you wish, then enter a name for the Project and then click OK, see below:

New Project

Step 2

New Silverlight Application window should appear, uncheck the box "Host the Silverlight Application in a new Web site" and then select the required Silverlight Version, see below:

New Silverlight Application

Step 3

A Blank Page named MainPage.xaml should then appear, see below:
MainPage.xaml

Step 4

Select from the Main Menu: "File" then "Add", then "New Project..." The "New Project" window should appear, select "Silverlight Class Library" with the Name "Expander" without the quotes, see below:
Add Silverlight Class Library Project

Step 5

In the "Choose the version of Silverlight you want to target from the list of installed Silverlight SDK's" choose the same version of Silverlight for example Silverlight 4 and click OK.
Then in the Solution Explorer for "Expander", click on the "Class1.vb" entry, then goto Properties and change the File Name to "Expander.vb" (without the quotes), see below:

Expander Class Properties

Step 6
In the "You are renaming a file. Would you also like to perform a rename in this project of all references to the code element 'Class1'?" choose Yes.
Right Click on the Entry for the "Expander" Project (not the Expander.vb) in Solution Explorer and choose "Add" then "New Folder", and give it the Name "Themes" (again without quotes), see below:

Expander Project Themes Folder

Step 7

Right Click on the Entry for the "Themes" Folder for the Expander Project, and choose "Add", then "New Item...", select "Silverlight Resource Dictionary" with the Name "Generic.xaml", without quotes, see below:

Generic.xaml Resource Dictionary

Step 8

In the XAML Pane for the Generic.xaml, in the "ResourceDictionary" tag type the following XAML namespace:
xmlns:local="clr-namespace:Expander"
See below:
Resource Dictionary Namespaces

Step 9

While still the XAML Pane for the Generic.xaml, above the "</ResourceDictionary>" tag and below the top "<ResourceDictionary>" tag, type the following XAML:

<Style TargetType="local:Expander">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:Expander">
        <Grid>
          <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ViewStates">
              <VisualStateGroup.Transitions>
                <VisualTransition GeneratedDuration="0:0:0.5"/>
              </VisualStateGroup.Transitions>
              <VisualState x:Name="Expanded">
                <Storyboard>
                  <DoubleAnimation Storyboard.TargetName="ContentScaleTransform"
                      Storyboard.TargetProperty="ScaleY" To="1" Duration="0"/>
                  <DoubleAnimation Storyboard.TargetName="RotateButtonTransform"
                      Storyboard.TargetProperty="Angle" To="180" Duration="0"/>
                </Storyboard>
              </VisualState>
            <VisualState x:Name="Collapsed">
              <Storyboard>
                <DoubleAnimation Storyboard.TargetName="ContentScaleTransform"
                    Storyboard.TargetProperty="ScaleY" To="0" Duration="0"/>
                <DoubleAnimation Storyboard.TargetName="RotateButtonTransform"
                    Storyboard.TargetProperty="Angle" To="0" Duration="0"/>
              </Storyboard>
            </VisualState>
          </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
          <Border BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}"
              CornerRadius="{TemplateBinding CornerRadius}"
              Background="{TemplateBinding Background}">
            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
              </Grid.RowDefinitions>
              <Grid Margin="3">
                <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="Auto"/>
                  <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <ContentPresenter Margin="3" Content="{TemplateBinding HeaderContent}"/>
                <ToggleButton Grid.Column="1" RenderTransformOrigin="0.5,0.5" Margin="3" x:Name="ExpandCollapseButton">
                  <ToggleButton.Template>
                    <ControlTemplate>
                      <Grid>
                        <Ellipse Width="20" Height="20" Stroke="#FFA9A9A9" Fill="AliceBlue"/>
                        <Path RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center" 
                            Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2"/>
                      </Grid>
                    </ControlTemplate>
                  </ToggleButton.Template>
                  <ToggleButton.RenderTransform>
                    <RotateTransform x:Name="RotateButtonTransform"/>
                  </ToggleButton.RenderTransform>
                </ToggleButton>
              </Grid>
              <ContentPresenter Grid.Row="1" Margin="5" Content="{TemplateBinding Content}" x:Name="Content">
                <ContentPresenter.RenderTransform>
                  <ScaleTransform x:Name="ContentScaleTransform"/>
                </ContentPresenter.RenderTransform>
              </ContentPresenter>
            </Grid>
          </Border>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

See below:
Expander Resource Dictionary

Step 10

Double Click on the Entry for the "Expander.vb" Class in Solution Explorer in the "Expander" project.
In the Code View for Expander above the "Public Class Expander" line type the following:
Imports System.Windows.Controls.Primitives
<TemplateVisualState(Name:="Collapsed", GroupName:="ViewStates"),
TemplateVisualState(Name:="Expanded", GroupName:="ViewStates"),
TemplatePart(Name:="Content", Type:=GetType(FrameworkElement)),
TemplatePart(Name:="ExpandCollapseButton", Type:=GetType(ToggleButton))>
See Below:
Expander.vb Imports and Template

Step 11

While still in the Code View for Expander.vb, below the "Public Class Expander" line type the following:
Inherits ContentControl

Private _useTransitions As Boolean = True
Private _collapsedState As VisualState
Private _toggleExpander As ToggleButton
Private _contentElement As FrameworkElement
See Below:
Expander.vb Declarations
.
Step 12
While still in the Code View for Expander.vb, above the "End Class" for "Public Class Expander", type the following Dependency Properties:
Public Shared ReadOnly HeaderContentProperty As DependencyProperty =
DependencyProperty.Register("HeaderContent", GetType(Object),
GetType(Expander), Nothing)

Public Shared ReadOnly IsExpandedProperty As DependencyProperty =
DependencyProperty.Register("IsExpanded", GetType(Boolean),
GetType(Expander), New PropertyMetadata(True))

Public Shared ReadOnly CornerRadiusProperty As DependencyProperty =
DependencyProperty.Register("CornerRadius", GetType(CornerRadius),
GetType(Expander), Nothing)
See Below:
Expander.vb Dependancy Properties
Step 13
While still in the Code View for Expander.vb, above the "End Class" for "Public Class Expander", type the following Properties:
Public Property HeaderContent() As Object
  Get
    Return GetValue(HeaderContentProperty)
  End Get
  Set(ByVal value As Object)
    SetValue(HeaderContentProperty, value)
  End Set
End Property

Public Property IsExpanded() As Boolean
  Get
    Return CBool(GetValue(IsExpandedProperty))
  End Get
  Set(ByVal value As Boolean)
   SetValue(IsExpandedProperty, value)
  End Set
End Property

Public Property CornerRadius() As CornerRadius
  Get
    Return CType(GetValue(CornerRadiusProperty), CornerRadius)
  End Get
  Set(ByVal value As CornerRadius)
    SetValue(CornerRadiusProperty, value)
  End Set
End Property
See Below:
Expander.vb Properties
Step 14
While still in the Code View for Expander.vb, above the "End Class" for "Public Class Expander", type the following Constructor and Sub:
Public Sub New()
  DefaultStyleKey = GetType(Expander)
End Sub

Private Sub ChangeVisualState(ByVal useTransitions As Boolean)
  If IsExpanded Then
    If _contentElement IsNot Nothing Then
      _contentElement.Visibility = Visibility.Visible
    End If
    VisualStateManager.GoToState(Me, "Expanded", useTransitions)
  Else
    VisualStateManager.GoToState(Me, "Collapsed", useTransitions)
    _collapsedState = TryCast(GetTemplateChild("Collapsed"), VisualState)
    If _collapsedState Is Nothing Then
      If _contentElement IsNot Nothing Then
        _contentElement.Visibility = Visibility.Collapsed
      End If
    End If
  End If
End Sub
See Below:
Expander.vb Constructor and Sub
Step 15
While still in the Code View for Expander.vb, above the "End Class" for "Public Class Expander", type the following Event Handlers:
Private Sub Toggle_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
  IsExpanded = Not IsExpanded
  _toggleExpander.IsChecked = IsExpanded
  ChangeVisualState(_useTransitions)
End Sub

Private Sub Collapsed_Completed(ByVal sender As Object, ByVal e As EventArgs)
  _contentElement.Visibility = Visibility.Collapsed
End Sub

Public Overrides Sub OnApplyTemplate()
  MyBase.OnApplyTemplate()
  _toggleExpander = TryCast(GetTemplateChild("ExpandCollapseButton"), ToggleButton)
  If _toggleExpander IsNot Nothing Then
    AddHandler _toggleExpander.Click, AddressOf Toggle_Click
  End If
  _contentElement = TryCast(GetTemplateChild("Content"), FrameworkElement)
  If _contentElement IsNot Nothing Then
    _collapsedState = TryCast(GetTemplateChild("Collapsed"), VisualState)
    If (_collapsedState IsNot Nothing) AndAlso (_collapsedState.Storyboard IsNot Nothing) Then
      AddHandler _collapsedState.Storyboard.Completed, AddressOf Collapsed_Completed
    End If
  End If
  ChangeVisualState(False)
End Sub
See Below:
Expander.vb Event Handlers
Step 16
Select Debug then the "Build Expander" option from the menu, see below:
Build Expander
Step 17
Return to the MainPage.xaml Designer View by selecting the "MainPage.xaml" Tab, or Double Clicking on the Entry for "MainPage.xaml" in Solution Explorer for the Main Project.
Then from the All Silverlight Controls section in the Toolbox select the Canvas control:
Canvas Control
Step 18
Draw a Canvas that fill the whole Page or in the XAML Pane between the "<Grid>" and "</Grid>" lines type the following XAML:
<Canvas Height="300" Width="400" HorizontalAlignment="Left" VerticalAlignment="Top" Name="Page">
</Canvas>
See below:
MainPage with Canvas
Step 19
Then from the Expander Controls section in the Toolbox select the Expander control:
Expander Control
Step 20
Draw an Expander on the Page (Canvas) by dragging the Button from the Toolbox onto the Canvas, then in the XAML Pane inbetween the "<Canvas>" and "</Canvas>" tags change the "my:Expander" XAML to the following:

<my:Expander Canvas.Left="75" Canvas.Top="25" Height="250" Width="250" HeaderContent="Expander">
  <my:Expander.Content>
    <StackPanel>
      <Button Margin="4" Padding="4" Content="Button One"/>
      <Button Margin="4" Padding="4" Content="Button Two"/>
      <Button Margin="4" Padding="4" Content="Button Three"/>
      <Button Margin="4" Padding="4" Content="Button Four"/>
    </StackPanel>
  </my:Expander.Content>
</my:Expander>

See below:
MainPage with Canvas and Expander
Step 21
Save the Project as you have now finished the Silverlight application. Select Debug then Start Debugging or click on Start Debugging:
Start Debugging
After you do, the following will appear in a new Web Browser window:
Application Running
Step 22
Click on the Round button with the Arrow to Collapse or Expand the Expander, see below:
Expander Control
Step 23
Close the Application and Browser window by clicking on the Close Button Close on the top right of the Application Window and Web Browser to Stop the application.
This is a simple example of how to create an Expander, it could be extended to support more Properties such as a HeaderContent Background colour for example. Try adding more features and make it your own!

Download Tutorial (458KB)

  Download Source Code (11.2KB)