HowTo: LINQ Over WCF – An easy domain table Cache

In many apps tables containing ID’s and names for a domain – guess they are called domain tables – needs to be cached in the biz layer.
In web apps the caching procedure is well documented and implemented in System.Web.Caching. Cache is a thread-safe object, that doesn’t requires explicit locking.
In non-web apps then what to do?
This article suggests to use LINQ over self-hosted WCF.

And why is that?
By using self-hosted WCF, the WCF host will run in its own thread in its own app domain and thereby be thread-safe and available for the code running in the main thread, which is the rest of the (console) app.
In the WCF-thread the data access is done using LINQ and the caching is done using Enterprise Library (EntLib) Caching.
The main-thread will communicate with the cache through WCF with named-pipes binding in order to get a fast in-proc communication pipe.
With this procedure you don’t have to think about multithreadding. That is a WCF behind-the-scenes trick.

The steps done here are:

  1. Create a data access layer using LINQ
  2. Wrap a WCF service around LINQ
  3. Optional step: Test the service with an external client
  4. Create an in-proc WCF service and client
  5. Cache the data set using Entlib

Tools: Visual Studio 2008, .NET3.5, EntLib 3.1.

Get the code her: http://www.box.net/shared/pqlckgydct

1. Using LINQ for data access

  • In a console app (called ConsoleAppLinqOverWcf in this sample) add a new item. Select template LINQ to SQL classes. Name the file “LinqDataClasses.dbml“. This opens up an Object Relational Designer (ORD).
  • In the property of LinqDataClassesDataContext change the serilization mode from none to unidirectional. This adds a reference to System.Runtime.Serilization and enables data to be transferred over WCF. 
  • Drag and drop a table from the Server Explorer to the ORD. In this sample table Employees from db AdventureWorks (http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004) is chosen.
    The db is installed on SQLExpress with has the connection string:
    “Data Source=localhost\sqlexpress;Initial Catalog=AdventureWorks;Integrated Security=True”.
    The ORD adds classes for the table in LinqDataClasses.designer.cs. Press “Show All Files” to see the file.
    Each row of the table is represented by an Employee object.
  • The actual data access code is written in the WCF service in the next step.

2. Create a WCF service

  • Add a new item. Select template WCF service. Name the file WcfService.cs. This will also create a IWcfService.cs file and an app.config file.
  • In IWcfService.cs add a method to get a list of employees from the table:
[OperationContract]
List<Employee> GetEmployeesByManagerId(int ManagerId);
        public List<Employee> GetEmployeesByManagerId(int ManagerIdWanted)
        {
            LinqDataClassesDataContext db = new LinqDataClassesDataContext();
            var query = from emp in db.Employees //same as db.GetTable<Employees>
                        where emp.ManagerID.Value == ManagerIdWanted
                        select emp;
            return query.ToList(); //same as query.ToList<Employee>();
        }
  • Start the service by running this code:
        static void Main(string[] args)
        {
            Console.WriteLine("*** Server ***");

            using (ServiceHost host = new
            ServiceHost(typeof(ConsoleAppLinqOverWcf.WcfService)))
            {
                host.Open();
                Console.WriteLine("Press <Enter> to terminate the Host application.");
                Console.WriteLine();
                Console.ReadLine();
            }
        }

3. Test the service

  • Now open app.config and select and copy the baseAddress: “http://localhost:8731/Design_Time_Addresses/ConsoleAppLinqOverWcf/WcfService/
  • While the service is running you create a new console program. In this sample called ConsoleApplicationWcfClient.
  • Add a Service Reference. Paste the copied baseAddress in the Address field and press go. The service is found if it is running. Name the reference WcfServer.
    If you press Show all files then you will see a bunch of autogenerated files nested under the service reference WcfServer. They implement interface IWcfService in the proxy class WcfServiceClient.
    VS2k8 also added a client configuration in app.config.
  • Execute this code:
        static void Main(string[] args)
        {
            Console.WriteLine("*** Client ***");

            WcfServer.WcfServiceClient svc = new WcfServer.WcfServiceClient();
            WcfServer.Employee[] lst; //note: It is not anymore a list: List<CWcfServer.Employee>

            lst = svc.GetEmployeesByManagerId(6);
            foreach (WcfServer.Employee emp in lst) {
                Console.WriteLine("BirthDate: " + emp.BirthDate.ToString());
            }
            Console.WriteLine("Press <Enter> to terminate the Client application.");
            Console.WriteLine();
            Console.ReadLine();
        }
  • Now the console should list a few lines of Birthdates.

4. Change the service to an in-proc service and client

        static void Main(string[] args)
        {
            Console.WriteLine("*** Server ***");
            IWcfService svc = null;
            try
            {
                svc = InProcFactory.CreateInstance<WcfService, IWcfService>();

                List<Employee> lst; 

                lst = svc.GetEmployeesByManagerId(6);
                foreach (Employee emp in lst)
                {
                    Console.WriteLine("BirthDate: " + emp.BirthDate.ToString());
                }
                Console.WriteLine("Press <Enter> to terminate the Host application.");
                Console.WriteLine();
                Console.ReadLine();
            }
            finally
            {
                if (svc != null)
                {
                    InProcFactory.CloseProxy(svc);
                }
            }
        }
  • Now the server is both a service server and a client. The service is running in its own thread as long as the proxy svc is open. Running the service will now print a few birthdates.
  • Try to let the service running while running the old client. You will see that the other binding from app.config still works, too, and also prints the birthdates. The service listens on two protocols. Cool eh?

5. Cache the data set

  • Download and build http://www.codeplex.com/entlib. I am using version 3.1.
    Why not 4.0? Because I already have 3.1 builded on my PC and in a production environment. So the answer is lazyness :-). 
  • Add a ref to Microsoft.Practices.EnterpriseLibrary.Common.dll, Microsoft.Practices.EnterpriseLibrary.Caching.dll and Microsoft.Practices.ObjectBuilder.dll in the WCF server.
  • Edit app.config using Enterprise Library Configuration Tool (ELCT). See http://msdn.microsoft.com/en-us/library/cc309106.aspx. Just add a Caching Application Block by rightclicking the app.config node and press add. Press save after the add.
    If none exists you cannot instanciate a cache.
    ELCT adds:
  <cachingConfiguration defaultCacheManager="Cache Manager">
    <cacheManagers>
      <add expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
        numberToRemoveWhenScavenging="10" backingStoreName="Null Storage"
        name="Cache Manager" />
    </cacheManagers>
    <backingStores>
      <add encryptionProviderName="" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=3.1.0.0, Culture=neutral, PublicKeyToken=9f3bbfe6f29ed5f5"
        name="Null Storage" />
    </backingStores>
  </cachingConfiguration>
  • Now you can use the cache in code. Add a cachemanager and instanciate it in the constructor:
    public class WcfService : IWcfService
    {
        private CacheManager primitivesCache;

        public WcfService() {
            this.primitivesCache = CacheFactory.GetCacheManager();
        }
  • Modify the service to use the cache:
        public List<Employee> GetEmployeesByManagerId(int ManagerIdWanted)
        {
            List<Employee> retVal;
            //Try to get it from cache:
            string key = "GetEmployeesByManagerId" + ManagerIdWanted.ToString();
            retVal = (List<Employee>)primitivesCache.GetData(key);
            if (retVal == null) { //was not cached
                LinqDataClassesDataContext db = new LinqDataClassesDataContext();
                var query = from emp in db.Employees //same as db.GetTable<Employees>
                            where emp.ManagerID.Value == ManagerIdWanted
                            select emp;
                retVal = query.ToList(); //same as query.ToList<Employee>();
                //do cache
                if (retVal != null)
                {
                    primitivesCache.Add(key, retVal); //You could add expiration etc as parm.
                }
            }
            return retVal;
        }
  • Test the cache:
    • Set a breakpoint on the (data access) line in the service:
      LinqDataClassesDataContext db = new LinqDataClassesDataContext();
    • Start the WCF server in debug mode. The line should be hit since nothing yet has been cached. Press F5 to continue.
      Don’t wait for too long or else there will be a WCF timeout.
      The data set will be printed as before in the console.
    • Start the WCF client. The breakpoint will not be hit, since the data is fetched from the cache. There will be printed the data set in the client console. 

With WCF we have created a simple distributed caching provider (when other bindings than namedPipeBinding is also added). EntLib will in future versions deliver some similar functionality:

NCache distributed caching provider for the Enterprise Library 4.0 Caching Application Block is released as CTP

Read more

You can get even more inspiration about same data access technique and update of the SQL server using a Silverlight frontend in this video:
http://blogs.msdn.com/swiss_dpe_team/archive/2008/03/17/silverlight-2-beta1-wcf-linq-to-sql-a-powerfull-combination.aspx

The End

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: