Implementing Cache Scavenging

Cache scavenging is a process for purging out-of-date cache records from the client cache. DCS does not implement cache scavenging by default, but by using the classes provided in the Microsoft.ConnectedIndustry.ServiceModel.ClientCache assembly, you can easily build applications that can examine the contents of the client cache and remove records that have expired.

The Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Cache class encapsulates the client cache held in a local SQL Server Compact Edition database. It provides the methods shown in the following table, which let you query and manipulate the records in the client cache.

Method or Property

Description

Add

This is method lets you programmatically add items to the client cache. You specify parameters that include a key to identify the object, an isolation key to distinguish the object if the same data is cached by different clients, the data for the object, and data that indicates when the cached data should expire.

Contains

This method lets you determine whether an object with the specified key and isolation key is currently available in the cache.

Flush

This method clears the cache.

GetData

This method lets you retrieve an object from the cache. You specify the key and the isolation key of the object.

GetOfflineData

This method returns the cached data if the cache has not yet expired as determined by the offline expiry configuration setting. Otherwise, this method deletes the cached data if the cache has expired based on the values of the absolute and the offline expiry configuration settings of the cache.

IsOfflineCacheExpired

This method determines whether the time represented by the offlineExpiration configuration setting for the cache has passed.

Remove

This is an overloaded method that removes an item from the cache. You specify the key and isolation key of the item as a string, and you can optionally provide a reason for removal as a value from the CachedItemRemoveReason enumeration. The Microsoft.ConnectedIndustry.ServiceModel.ClientCache.CachedItemRemoveReason enumeration defines the removal reason values. Accepted values are Expired, Removed, Scavenged, and Unknown.

RemoveItemFromCache

This method removes an item from the cache and operates in the same way as the Remove method.

Count

This property returns the number of items in the client cache.

CurrentCacheState

This property returns a DataTable object that contains a copy of the items in the cache.


Dd632018.note(en-us,MSDN.10).gifNote:
The Cache class implements the Microsoft.ConnectedIndustry.ServiceModel.ClientCache.ICacheOperations interface. This interface defines the RemoveItemFromCache method and the CurrentCacheState property.

You obtain a reference to the Cache object that implements the client cache by using the Cache property of a Microsoft.ConnectedIndustry.ServiceModel.ClientCache.CacheManager object. Each partition in the cache has its own cache manager. You create a cache manager object for a partition by using the static GetCacheManager method of the Microsoft.ConnectedIndustry.ServiceModel.ClientCache.CacheFactory class. The GetCacheManager method takes the name of the partition as its parameter. The following code example shows how to create a reference to the cache for the partition. The reference is named ResponseCache.


                    using Microsoft.ConnectedIndustry.ServiceModel.ClientCache;
...
string name = "ResponseCache";
CacheManager cacheManager = CacheFactory.GetCacheManager(name);
Cache cache = cacheManager.Cache;

                  

The records in the client cache contain fields that indicate when the records were last refreshed, and information that indicate how long the records are valid after they are refreshed. Records that are no longer valid have expired and should be removed.

Dd632018.note(en-us,MSDN.10).gifNote:
The lifetime of records in the cache is governed by the absoluteExpiration property of the caching policy of the service, as well as the properties of the <expiration> element for each partition in the <dcs.clientCache> section of the client application configuration file. For more information, see Configuring Caching Policy for an Operation and Configuring a Client Application to Support Response Caching.

You can remove expired records from the client cache by using a Microsoft.ConnectedIndustry.ServiceModel.ClientCache.ExpirationTask object. The ExpirationTask class provides the DoExpirations method, which iterates through the records in the client cache and removes those that are no longer valid. You create an ExpirationTask object for a cache by providing a reference to the cache as the parameter to the ExpirationTask constructor. You must specify the reference to the cache object by using the ICacheOperations interface. The following code example shows how to create an ExpirationTask object from a Cache object, and call the DoExpirations method to purge the cache of out-of-date records.


                    using Microsoft.ConnectedIndustry.ServiceModel.ClientCache;
...
Cache cache; // populate the cache object by using the CacheManager object
...
ICacheOperations cacheOperations = cache;
ExpirationTask expirationTask = new ExpirationTask(cacheOperations);
expirationTask.DoExpirations();

                  

The GetCacheManager method of the CacheFactory class requires you to specify the partition name of the cache that you want to manage. You can retrieve this information from the <cacheManager> section in the <dcs.clientCache> section of the application configuration file. The Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.CacheManagerSection class provides a convenient mechanism to parse the information in the <cacheManager> section and obtain the configuration settings for the items in this section. You create an instance of the CacheManagerSection class by using the static method GetSection. The CacheManagerSection class is located in the Microsoft.ConnectedIndustry.ServiceModel.ClientCache assembly.

The Partitions property of the CacheManagerSection class is a collection that describes each of the <partition> sections in the configuration file for the cache manager. The Partitions property is a collection of PartitionElement objects. The PartitionElement class exposes public properties for each of the elements specified in the <partition> section of the cache manager configuration file. The following table summarizes these properties.

Property

Description

ExpirationElement

This property is a Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.ExpirationElement object. The ExpirationElement class contains properties that describe the absolute and sliding expiration values of the partition.

IsolationLevel

This is a string property that contains the value of the isolation key for the partition.

Name

This is a string property that contains the name of the partition.

OfflineExpiration

This property is a Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.OfflineExpirationElement object. The OfflineExpirationElement class contains properties that describe the offline absolute and sliding expiration values of the partition.

Store

This property is a Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.StoreElement object. The StoreElement class contains properties that describe the name and location of the database holding the cache.


The following code example shows how to create a CacheManagerSection object and use it to retrieve the name and location of each cache partition described in the configuration file.


                    using Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration;
...
CacheManagerSection section = CacheManagerSection.GetSection();
foreach (PartitionElement partitionElement in section.Partitions)
{
    string partitionName = partitionElement.Name;
    StoreElement location = partitionElement.Store;
    ...
}

                  

The following example shows the code for a service that implements cache scavenging. It runs as a Windows service and polls the cache periodically at a configured interval looking for expired records to purge.


                    /*=============================================================================

Sample for Microsoft Distributed Connectivity Services V1.0

File:    ScavengingService.cs 
Area:    Client Cache
Summary: ScavengingService implements a windows service that removes
         the expired cache records from the cache. 

=============================================================================*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Configuration;
using Microsoft.ConnectedIndustry.ServiceModel.ClientCache;
using Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration;
using Microsoft.ConnectedIndustry.ServiceModel.Common;

namespace ScavengingSample
{
    public partial class ScavengingService : ServiceBase
    {
        private ExpirationPollTimer expirationPollTimer;
        internal const string ModuleName = "CacheService";

        public ScavengingService()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Used only for testing purposes
        /// </summary>
        public void Start()
        {
            OnStart(null);
        }

        protected override void OnStart(string[] args)
        {
            InitializePollTimer();

        }

        protected override void OnStop()
        {
            TerminatePollTimer();

        }

        private void InitializePollTimer()
        {
            try
            {
                // read poll timer from configuration

                CacheServiceSection section = CacheServiceSection.GetSection();
                int pollInterval = section.Scavenging.ExpirationPollTimer;
                Tracer.Write(TraceEventType.Information, ModuleName, "InitializePollTimer: the configured poll timer interval is {0}", pollInterval);
                expirationPollTimer = new ExpirationPollTimer();
                expirationPollTimer.StartPolling(new TimerCallback(PollTimerCallback), pollInterval * 60 * 1000);
                Tracer.Write(TraceEventType.Information, ModuleName, "InitializePollTimer: poll timer initialized successfully");
            }
            catch (Exception ex)
            {
                Tracer.Write(TraceEventType.Warning, ModuleName, "InitializePollTimer: Error initializing the poll timer, error is {0}", ex.Message);
            }
        }

        private void TerminatePollTimer()
        {
            if (expirationPollTimer != null)
            {
                expirationPollTimer.StopPolling();
            }
        }

        public void PollTimerCallback(object input)
        {
            Tracer.Write(TraceEventType.Verbose, ModuleName, "PollTimerCallback: starting expiration task");
            CacheManagerSection section = CacheManagerSection.GetSection();
            foreach (PartitionElement partitionElement in section.Partitions)
            {
                string name = partitionElement.Name;
                StoreLocation location = partitionElement.Store.Location;
                if (location == StoreLocation.User)
                {
                    Tracer.Write(TraceEventType.Verbose, ModuleName, "PollTimerCallback: The partition {0} has a store location in the user profile, bypassing it", name);
                    continue;
                }

                Tracer.Write(TraceEventType.Verbose, ModuleName, "PollTimerCallback: starting expiration for partition {0}", name);
                CacheManager cacheManager = CacheFactory.GetCacheManager(name);
                ICacheOperations cacheOperations = cacheManager.Cache as ICacheOperations;
                ExpirationTask expirationTask = new ExpirationTask(cacheOperations);
                expirationTask.DoExpirations();

                Tracer.Write(TraceEventType.Verbose, ModuleName, "PollTimerCallback: expiration for partition {0} completed", name);
            }
        }
    }
}

                  

The Scavenging Service uses the following configuration information:

  • The partition details of the cache to be scavenged. The service reads this information from the <cacheManager> section of the configuration file.
  • The polling interval to use, in minutes. The service reads this value from the expirationPollTimer property of the <scavenging> element in the <cacheService> section of the configuration file. You can use the Scavenging property of the Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.CacheServiceSection class to read the <scavenging> element. The Scavenging property is a Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.ScavengingElement object. You can extract the value of the expirationPollTimer from the configuration file by reading the ExpirationPollTimer property of the ScavengingElement object.

The following example shows a typical configuration file for the Scavenging Service.


                    <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="cis.serviceModel"
                 type="Microsoft.ConnectedIndustry.ServiceModel.Application.Configuration.ServiceModelSectionGroup,
                 Microsoft.ConnectedIndustry.ServiceModel.Application,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35">
      <section name="caching" type="Microsoft.ConnectedIndustry.ServiceModel.Configuration.CachingSection, Microsoft.ConnectedIndustry.ServiceModel,Version=1.0.0.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </sectionGroup >
    <sectionGroup name="dcs.clientCache">
      <section name="cacheManager" type="Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.CacheManagerSection, Microsoft.ConnectedIndustry.ServiceModel.ClientCache, version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <section name="cacheService" type="Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.CacheServiceSection, Microsoft.ConnectedIndustry.ServiceModel.ClientCache, version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <section name="cacheFilter" type="Microsoft.ConnectedIndustry.ServiceModel.ClientCache.Configuration.CacheFilterSection, Microsoft.ConnectedIndustry.ServiceModel.ClientCache, version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </sectionGroup>
  </configSections>

  <system.diagnostics>
    <sources>
      <source name="CIS" switchValue="Information, Verbose, ActivityTracing, Critical">
        <listeners>
          <add name="FileListener"/>
        </listeners>
      </source>
      <source name="Microsoft.ConnectedIndustry.ServiceModel" switchValue="Information, Verbose, Warning, ActivityTracing">
        <listeners>
          <add name="Debug"/>
          <add name="FileListener"/>
        </listeners>
      </source>

    </sources>
    <sharedListeners>
      <add logDirectory="C:\Program Files\Microsoft CIS\DCS\V1.0\Log\Scavening" baseFileName="ScaveningLog" maxFileNumber="5" maxFileSize="100000" textFormatter="{SOURCE} - {MESSAGE}{BR}" type="Microsoft.ConnectedIndustry.ServiceModel.Common.RollFileListener,  Microsoft.ConnectedIndustry.ServiceModel.Common, version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="FileListener">
        <filter type=""/>
      </add>
      <add name="Debug" type="System.Diagnostics.DefaultTraceListener" />
    </sharedListeners>
  </system.diagnostics>

  <dcs.clientCache>
    <cacheManager>
      <partitions>
        <partition name="MSFTDCS" isolationLevel="">
          <store location="Machine" database="CacheDatabase" table="DCSCache" encryption="true" />
          <expiration  absolute="" sliding="" />
        </partition>
      </partitions>
    </cacheManager>
    <cacheService>
      <scavenging expirationPollTimer="1" />
    </cacheService>
    <cacheFilter enabled="true" defaultPartition="MSFTDCS" />
  </dcs.clientCache>
</configuration>

                  
Show: