Use Caching to Turbo-Boost your LightSwitch Apps (Saar Shen)

Use Caching to Turbo-Boost your LightSwitch Apps (Saar Shen)

Rate This
  • Comments 5

Making use of a cache to “turbo-boost” the retrieval and presentation of read-only data is a common need in data-based applications. In this post, I'll walk you through how to speed up your LightSwitch application by using cached data. This is particularly useful for screens that use read-only reference data or data that is not updated that often.

For this example, I’ll create a Browse Orders screen to show an order list from the Northwind database. Then I’ll put caching into the application to see the performance gains. First, create a LightSwitch project. Let’s call it OrderManager for example. Then, we’ll add a SQL data source to Northwind and import the following entities: Customers, Orders, Shippers.

If you don’t have the database yet, please refer to How to: Install Sample Database to install one.

image

When the data source is added, we can create a screen to show a list of orders with some other information like this:

image

To make it easier to see the performance difference, I’ll uncheck support paging for the Orders query on the Browse Orders screen.

image

Now, we can press F5 to see what it will look like in the browser:

image

Next, let's change the implementation of the data source a little. I’ll use a RIA service to replace the use of directly attaching to the Northwind database. That way, we’ll have a spot to put in our cache logic.

There are two main things the RIA service needs to do. It needs to fetch data from Northwind database, and it needs to provide the data as a RIA service for our LightSwitch application.

To fetch data from a SQL database, we will make use of the Entity Framework. Let’s create a class library in the solution named OrderDataService and then add a new ADO.NET Entity Data Model.

image

Since we already have the database, we will choose Generate from database from the Entity Data Model Wizard, and set the SQL connection string the same as we would connect to plain SQL Database. On the next page, choose Entity Framework 5.0 (EF). Refer to EF and Visual Studio 2013 Preview for more information.

When it comes to Choose Your Database Objects and Settings, let’s check Customers, Orders and Shippers like we did above.

image

Build the solution to make sure there are no build errors. This will generate files for database access. Now that we have the classes to access the database, we are going to code the RIA service. First, in the class library, add a reference to System.ServiceModel.DomainService.Server.

image

We have to write two pieces of code. First is the entity that is used to transfer data from the class library to LightSwitch. The other is calling into the Entity Framework service to fetch the results and return it as RIA service.

Here's the code for the entity. It basically wraps up the properties that we want to transfer between the data source and the LightSwitch project. 

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;

namespace OrderDataService
{
    [DataContract]
    public class OrderEntity
    {
        [Key]
        [DataMember]
        public int ID { get; set; }
        [DataMember]
        public DateTime? OrderDate { get; set; }
        [DataMember]
        public string CustomerContactName { get; set; }
        [DataMember]
        public string ShipperCompanyName { get; set; }
        [DataMember]
        public string ShipperPhone { get; set; }
    }
}

VB.NET

Imports System.Runtime.Serialization
Imports System.ComponentModel.DataAnnotations

<DataContract>
Public Class OrderEntity
    <Key>
    <DataMember>
    Property ID As Integer
    <DataMember>
    Property OrderDate As Nullable(Of DateTime)
    <DataMember>
    Property CustomerContactName As String
    <DataMember>
    Property ShipperCompanyName As String
    <DataMember>
    Property ShipperPhone As String
End Class

You should notice that there are a lot of attributes on the class and its properties. The [DataContract] attribute on the class makes the class serializable. And the [DataMember] attribute makes properties serializable. It is necessary to contain a key field, so we need to add a reference to System.ComponentModel.DataAnnotations and then mark the ID property with the [Key] attribute.

The other class gets data from the Entity Framework, converts it to a list, and returns it as a query of OrderEntity. This class has to inherit from the DomainService class to work as a RIA service. Refer to Guidelines for Creating WCF RIA Services for LightSwitch for more information about creating a RIA service compatible with LightSwitch.

C#

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel.DomainServices.Server;

namespace OrderDataService
{
    public class DataService : DomainService
    {
        [Query(IsDefault = true)]
        public IQueryable<OrderEntity> GetAllOrders()
        {
            using (NorthwindEntities dbContext = new NorthwindEntities())
            {
                DateTime start = DateTime.Now;
                List<OrderEntity> result = new List<OrderEntity>();
                var orderList = dbContext.Orders.Include(o => o.Customer).Include(o => o.Shipper);
                foreach (var item in orderList)
                {
                    OrderEntity newEntity = new OrderEntity()
                    {
                        ID = item.OrderID,
                        OrderDate = item.OrderDate,
                        CustomerContactName = item.Customer != null ? item.Customer.ContactName : null,
                        ShipperCompanyName = item.Shipper != null ? item.Shipper.CompanyName : null,
                        ShipperPhone = item.Shipper != null ? item.Shipper.Phone : null
                    };
                    result.Add(newEntity);
                }
                DateTime end = DateTime.Now;
                Trace.WriteLine(string.Format("Query without Cache takes: {0}ms.", 
(end - start).TotalMilliseconds));
return result.AsQueryable(); } } } }

VB.NET

Imports System.ServiceModel.DomainServices.Server
Imports System.Runtime.Caching

Public Class OrderDataService
    Inherits DomainService
    <Query(IsDefault:=True)>
    Function GetAllOrders() As IQueryable(Of OrderEntity)
        Using DbContext As New NorthwindEntities()
            Dim StartTime = DateTime.Now
            Dim Result = New List(Of OrderEntity)
            Dim OrderList = DbContext.Orders.Include("Customer").Include("Shipper")
            For Each Item In OrderList
                Dim NewEntity = New OrderEntity With {
                    .ID = Item.OrderID,
                    .OrderDate = Item.OrderDate}
                If Not Item.Customer Is Nothing Then
                    NewEntity.CustomerContactName = Item.Customer.ContactName
                End If
                If Not Item.Shipper Is Nothing Then
                    NewEntity.ShipperCompanyName = Item.Shipper.CompanyName
                    NewEntity.ShipperPhone = Item.Shipper.Phone
                End If
                Result.Add(NewEntity)
            Next
            Dim EndTime = DateTime.Now
            Trace.WriteLine(String.Format("Query without Cache takes: {0}ms.", 
(EndTime - StartTime).TotalMilliseconds))
Return Result.AsQueryable() End Using End Function End Class

(Note: I’m keeping this example simple, but please note for more accurate time measurement you should use the stopwatch class instead of DateTime.)

We'll mark the method with the [Query] attribute, and set IsDefault = true. It is a requirement that there has to be at least one default query for an entity type. Build and make sure there are no build errors. To evaluate the performance gain, put a line before return statement which will calculate the amount of time the query takes. It will write the amount of time to the Output window when the code is executed.

Next, build the solution to make the RIA service available to consume in LightSwitch. Go back to the LightSwitch project, add a new data source and select WCF RIA service then add a reference to the OrderDataService project. You will see the RIA service in the Attach Data Source Wizard like this:

image

You will also see the entity with the fields we created. Give the data source a name of OrderDataSource.

image

Before we create a new screen we need to copy the configuration for the Entity Framework from App.Config under OrderDataService to Web.config in the OrderManger.Server project. To do that, we need to open the App.Config and copy three sections, configSections, add and entityFramework, to the right place in Web.config.

image

We then can create a new screen called BrowseOrderRIA to test that the RIA service works correctly.

image

On this screen, uncheck support paging for the screen query in the properties sheet. Finally, set the ‘BrowseOrderRIA’ screen as Home Screen by right-clicking on the screen in the Solution Explorer, F5 to build and debug.

Let’s check the output window for the time that is spent on the query – 671.7278ms.

image

Now we’ll add caching. Add a reference to System.Runtime.Cache from NorthwindDataService project and add a new query to the OrderDataService class called GetAllOrdersCache.

C#

        [Query]
        public IQueryable<OrderEntity> GetAllOrdersCache()
        {
            DateTime start = DateTime.Now;
            const string cacheKey = "OrderListCacheKey";
            MemoryCache cache = MemoryCache.Default;
            List<OrderEntity> result = null;
            if (cache.Contains(cacheKey))
            {
                result = (List<OrderEntity>)cache[cacheKey];
                DateTime end = DateTime.Now;
                Trace.WriteLine(string.Format("Query with cache takes: {0}ms.", 
(end - start).TotalMilliseconds));
return result.AsQueryable(); } else { result = GetAllOrders().ToList(); CacheItem cacheItem = new CacheItem(cacheKey, result); cache.Add(cacheItem, new CacheItemPolicy()
{ AbsoluteExpiration =
DateTimeOffset.Now.AddMinutes(5) }); } return result.AsQueryable(); }

VB.NET

    <Query>
    Function GetAllOrdersCache() As IQueryable(Of OrderEntity)
        Dim StartTime = DateTime.Now
        Const CacheKey = "OrderListCacheKey"
        Dim Cache = MemoryCache.Default
        Dim Result As List(Of OrderEntity)
        If (Cache.Contains(CacheKey)) Then
            Result = Cache(CacheKey)
            Dim EndTime = DateTime.Now
            Trace.WriteLine(String.Format("Query with Cache takes: {0}ms.", 
(EndTime - StartTime).TotalMilliseconds))
Else Result = GetAllOrders().ToList() Dim CacheItem = New CacheItem(CacheKey, Result) Cache.Add(CacheItem, New CacheItemPolicy() With
{.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(5)}) End If Return Result.AsQueryable() End Function

As you can see, it now checks the cache before each query to the database. If the data is already in the cache, it returns the cached data. Otherwise, it will fetch the data through EF, save the result to the cache with an expiration time and then return the result.

Build the solution and go back to the LightSwitch project, update the RIA Data Source by right clicking on the OrderDataSource in Solution Explorer and you should see a new query underneath the entity named GetAllOrderCache.

image

Now create a new browse screen using the query. Remove the support for paging by selecting the query and un-checking the property in the properties window of the screen designer:

image

Set this new page as Home Screen and F5 to debug it. The first time you run the screen, it will pull the data from the database. Refresh the browser and now the data is cached. Notice that it takes no time at all!

image

600ms may not sound like a big gain, however, this implementation saves a roundtrip from the web server to the data source. This can be a huge gain when your application needs to scale.

To recap, there are 3 steps to building an in-memory cache that can help speed up a LightSwitch application particularly as your scalability needs grow.

Step 1. Build the application as you normally would.

Step 2. Change the data source to introduce a middle-tier that can represented as a typed entity in your LightSwitch application.

Step 3. Add your cache logic to the middle tier.

Have fun!

References

If you want to learn more about the cache, I encourage you check out these resources.

Please visit our forums for troubleshooting and feedback on Visual Studio LightSwitch. We look forward to hearing from you!

- Saar Shen, Tester, LightSwitch Team

Leave a Comment
  • Please add 4 and 1 and type the answer here:
  • Post
  • Thanks Saar, this is quite a useful article.

    How does filtering from the client side affect this method of caching? Typically the filtering is translated down to SQL with the filtering happening at the database level, so I presume this method of caching will only work if you are not doing any filtering?

    Also, what about column sorting? Different users will be sorting by different columns?

    Thanks again

  • @Xander,

    Thank you so much for the comment!

    It is a great point to consider filtering data. I think the answer depends on how to implement the filter.

    One implementation is making use of the parameter for the service.

    For example, we can create a new method like this:

    [Query]

    public IQueryable<OrderEntity> GetOrderByIDCached(int orderId)

    {

       var result = GetAllOrdersCache().Where(o => o.ID == orderId);

       return result;

    }

    Since GetOrdersCache() will making use of cache, putting a filter on it will take almost no time.

    Take one step further, we can make cache to the newly created method's result as well to avoid heavy deserialization on web server. There could be levels of caches.

    I think the same applies to sort. What do you think?

    Thanks again for the comment.

    -Saar

  • do not use DateTime as stopwatch. its very inaccurate.

    use msdn.microsoft.com/.../system.diagnostics.stopwatch.aspx

  • Good catch, Pavel!

  • Thanks for the tuto. It works fine in local but I have a problem when deploying my application to Azure. I don't know what to put in the connection strings when publishing. Can anyone help ?  

Page 1 of 1 (5 items)