Making use of a cache to turbo-boost the presentation of read-only data is a common solution. In this post, I'll walk with you to speed up a LightSwitch HTML Application.
To make an example, I’ll create Browse Orders screen to show an order list from Northwind database. Then I’ll put cache in to see the performance gains.
First, we create a LightSwitch project. Let’s call it OrderManager for example. Then, add a SQL data source to Northwind and imports the following entities: Customers, Orders, Shippers.
If you don’t have the database yet, please refer How to: Install Sample Database to install one.
When the data source is added, we can create a screen to show a list of order with some other info like this:
To make it easier to see the performance difference, I’ll uncheck support paging for the query of Orders on Browse Orders screen.
Now, we can press F5 to see what it will look like in browser:
Next, let's change the implementation of the data source a little. I’ll use a RIA service to replace the use of direct attaching to the Northwind. Thus, we’ll have a spot to put in cache logic.
There are mainly two task for the RIA service, to fetch data from Northwind database and to provide the data as a RIA service for LightSwitch application.
To fetch data from a SQL database, we will make use of Entity Framework. Let’s create a class library to the solution named OrderDataService and then add a new ADO.NET Entity Data Model.
Since we already have the database, we will choose Generate from database from Entity Data Model Wizard, and set the SQL connection string the same as we connect to plain SQL Database. On the next page, I choose Entity Framework 5.0 (EF) since EF 6 is still in its early stage and it is not our main topic. Refer EF and Visual Studio 2013 Preview to read details. When it comes to Choose Your Database Objectsand Settings, let’s check Customers, Orders and Ships as what we did before.
Build the solution making sure there's no build errors. And there will be generated files for database access. Now that we have the classes to access database, we are going to provide the RIA service. First, in the class library, add a reference to System.ServiceModel.DomainService.Server.
We have to write 2 pieces of code. First is the entity that is used to transfer data from class library to LightSwitch, another 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.
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; } } }
You already noticed that there are a lot of attributes on class and properties. [DataContract] attribute on the class make the class serializable. And [DataMember] attribute make properties serializable. It is better to contain a key field, and we add reference to System.ComponentModel.DataAnnotations and then marked the ID property with [Key] attribute.
The other class gets data from Entity Framework, convert it to list, and return it as a query of OrderEntity. This class has to inherit from DomainService class to work as a RIA service. Refer Guidelines for Creating WCF RIA Services for LightSwitch for more information about creating a RIA service.
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(); } } } }
We'll mark the method with [Query] attribute, set IsDefault = true since this is a requirement that there has to be at least 1 default query for an entity type. Build and make sure there's no build errors. To simply evaluate the performance gain, I put a line before return to calculate the amount of time the query takes. It will write the amount of time to Output window when the code is executed.
Let’s build the solution to make RIA service ready. Switch back to the LightSwitch Application, add reference to OrderDataService project from OrderManager.Server and add new WCF RIA Service. You should be able to see the RIA service in the Attach Data Source Wizard like this:
And you'll see the entity with the fields we created before and let’s give the data source a name of OrderDataSource
Before we create new screen we need to copy configuration for Entity Framework from App.Config under OrderDataService to Web.config in OrderManger.Server project. To do that, we need to open App.Config, copy 3 sections(configSections, add and entityFramework) to the right place into Web.config.
We then create a new screen called BrowseOrderRIA to test the RIA service works correctly.
Uncheck support paging for the screen query, set ‘BrowseOrderRIA’ as Home Screen, build and F5 to debug. Let’s check the output window for the time that is spent on the query – 671.7278ms.
Now, add a reference to System.Runtime.Cache from NorthwindDataService project and add a new query to OrderDataService class called GetAllOrdersCache.
[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(); }
As you can see, it check the cache before each query of the OData feed. If the data is already in the cache, return it directly. Otherwise, fetch the data through EF, save the result to cache with an expiration time and return the result. Build the solution and turn back to the LightSwitch Application, update the RIA Data Source and we will see a new query underneath the entity named GetAllOrderCache.
Let’s create a new browse screen using the query, remove the support for paging:
Set this new page as Home Screen and F5 to debug it. For the first time, it is same as it was. Refresh in the browser and after the data is cached, it takes, well, 0ms.
Around 600ms may not big gain here. However, this implementation saves a roundtrip from IIS server to data source. It can save a lot of time under the situation of concurrency data retrieving especially when data server’s under heavy load and it can save money whenever data retrieve costs.
OK, putting things together. There are 3 steps to reach the goal of using an Memory Cache to speed up a LightSwitch Application.
Step 1. Finish what the application is supposed to do.
Step 2. Change the data source to introduce a middle tier.
Step 3. Add cache logic to the middle tier.
It may be a good idea to start with step 2 if we already know that we are going to use cache. Have fun!
References
If you want to learn more about the cache, I encourage you check out these resources.
- Using MemoryCache in .NET 4.0: http://www.codeproject.com/Articles/290935/Using-MemoryCache-in-Net-4-0
- The art of simplicity – Caching in .NET 4: http://bartwullems.blogspot.com/2011/02/caching-in-net-4.html
- 10 Caching Mistakes that Break your App: http://www.codeproject.com/Articles/115107/Ten-Caching-Mistakes-that-Break-your-App
Please visit our forums for troubleshooting and feedback on Visual Studio 2013 Preview. We look forward to hearing from you!