The title lends itself to a bit thought. Why would the developer feel the need hold a static reference to the MetadataWorkspace of an Entity Framework (EF) model from within their WCF service-oriented applications? The premise being that the WCF services are using EF to serve up data from a backend data store. The answer is twofold. Firstly, loading the EF metadata data into memory is expensive and secondly and most importantly is that EF has a depreciation policy for the metadata cache. The deprecation policy kicks in after the predetermined threshold has been reach (typically 20 minutes of idle time) and removes the metadata from cache. This means that the expense of loading EF metadata into memory is incurred after the idle time has been reached and the deprecation policy has been completed. In laymen terms, the first WCF client call making use of an EF query is subject to a warm-up delay while EF loads the metadata cache. The larger the model, the longer that ‘first’ user of the service must wait for their results. To maintain consistent service response rates, one can hold a reference to the MetadataWorkspace object to ensure that EF doesn’t remove it from its cache.
In short, the solution submitted in this blog pertains to those applications exhibiting lengthy EF startup durations, periodic idle time with their services and a client base that expects predictable service response times.
When an EntityConnection is created, the Entity Framework will load the conceptual, storage and mapping between the conceptual model and the storage model. You can see this if you examine the CSpace (conceptual), SSpace (storage), and CSSpace (conceptual/storage mapping) item collection properties of the MetadataCache instance once the EDM has been loaded. For example, the following code opens an EntityConnection and a QuickWatch of the MetadataWorkspace property of the connection reveals the CSpace, CSSpace, OCSpace, OSpace and SSpace collection properties. With this model we have 274 items in the conceptual model, 314 items in the storage model and 1 item in the conceptual to storage mapping.
Note: the model was created from the AdventureWorksLT2008R2 which is included with the SQL Server 2008R2 product sample databases on codeplex.
Note that object model and mapping between the object and conceptual model is not loaded when an EntityConnection is created, the associated item collections are empty. The CountMetaDataItems helper function was created to count the items in each collection.
So how do we load the object model? The MetadataWorkspace object constructor sound promising. It can load all the .csdl, .msl and ssdl embedded resources from the available assemblies. Let’s give it a try. The following code assumes that model resources are embedded into the executing assembly. If not, the assembly fully qualified name would have to be used together with a known type to return the assembly for the model. An example of which is commented out in the code snippet below. If your model is created in a separate assembly, then you must load the assembly using the assembly qualified name of a type within the model as shown in the lines 1-4 in the code snippet below.
As we can see from the output above, the object model is not loaded. This is because the object model is loaded during view generation or during the first query referencing an entity set. The generated views are then stored inside the object model. So what this means is that we can load the conceptual, storage and conceptual to storage mapping into a MetadataWorkspace instance and then instantiate a new EntityConnection using this MetadataWorkspace instance. The EntityConnection instance can then be used to in the ObjectContext constructor. The only thing left would be to build a LINQ query which becomes converted into a command tree upon which the object model is loaded into cache. A code sample is shown below.
WCF Services Initialization
What’s left is to initialize the EF MetadataWorkspace during startup of the WCF service and hold a static reference which can be used for subsequent EF queries. Fortunately WCF offers a number of trivial options to perform ‘hook’ into the service startup. Below is a list of a number of options, you can read this blog entry for a more descriptive read. I will show a straight forward sample of initializing the MetadataWorkspace by creating custom ServiceHostFactory and ServiceHost classes and implementing the ServiceHostBase.InitializeRunTime() method.
- Global.asmx file
- Provides that the WCF service is in ASP.NET compatibility mode
- Called during startup of WCF service
- Static AppInitialize() method
- Must be place into the App_Code folder
- Implementing ServiceHostFactory and a custom ServiceHost
- override the InitializeRuntime() method
- WCF ServiceHostEvents
WCF Sample Implementation
Create a class to hold a global static reference to the MetadataWorkspace which can be globally referenced.
Create a WCF Service called AWSales. Modify the interface to include a operational contract, for example GetCustomers(string firstName).
Create a custom service host by deriving from ServiceHost.
Create a custom ServiceHostFactory to instantiate the derived ServiceHost.
Implement the service contract and utilize the static reference to MetadataWorkspace when creating your EntityConnections.
Set the ‘Factory’ attribute in the .svc file.
The practice suggested in this blog does not pertain to all WCF services. For those smaller or medium size EF models it doesn’t really matter if the Entity Framework deprecates the metadata cache after periods of inactivity, the model will load quite quickly and in the end, the user will never know this occurred. But for larger models, where loading time is much longer and the experience of the end user could be impacted, the technique outlined in this blog may be worth exploring.