How to: Enable Persistence for a Durable Service
Durable services are Windows Communication Foundation (WCF) services that are designed to be durable and long-running, and to survive application and server restarts. Durable services are persisted using either the out-of-the-box provider created by a SqlPersistenceProviderFactory or a custom persistence provider derived from the abstract class PersistenceProvider or LockingPersistenceProvider.
Warning
To use SqlPersistenceProviderFactory, create your persistence database using the scripts from .NET Framework 3.0. These scripts install by default to the C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN*.sql directory.
The LockingPersistenceProvider abstract class derives from PersistenceProvider and exposes functionality for explicitly locking, unlocking, loading, saving, and deleting service state information. Service state information is locked before save or delete operations can take place, so that data contentions between multiple hosts do not occur. Before a lock can be acquired on state data, the following two criteria must be met:
The record’s lockOwner must be null.
The record’s lockExpiration value must be less than the current DateTime (that is, any previous locks must be expired).
When the lock is acquired, the record’s lockOwner field is set to the host ID of the locking host (this is unique to each provider instance and is generated in the provider’s constructor), and the record’s lockExpiration value is set to DateTime.UtcNow plus the lock time-out value. (This is either the value provided to the persistence provider’s constructor, or a default value defined in the provider implementation class. In the out-of-the-box SqlPersistenceProviderFactory implementation, the default is zero, which means that locking is not used by default.) If the lock is already held, the lock time-out value is also updated if Lock or Load is called with the lockInstance parameter set to True. The lock time-out is also updated if the lock is already held and Save is called with the unlockInstance parameter set to False.
The SqlPersistenceProviderFactory creates an out-of-the-box implementation of the LockingPersistenceProvider abstract class. SqlPersistenceProviderFactory targets SQL 2005 as a persistence store, and can persist state information in either text or binary formats.
The SqlPersistenceProviderFactory can be created with up to three constructor parameters. The following table shows the three constructor parameters.
Parameter | Description |
---|---|
lockTimeout |
The time-out for lock ownership. The service state instance is automatically unlocked after this time-out. A time-out of zero (the default) causes locking not to be used, and a value of TimeSpan.MaxValue causes locks to never expire. |
connectionString |
The string that contains the parameters used to connect to the SQL database. Note that when configuring the persistence provider using a configuration file, the connection string is placed in the connectionStrings node, rather than alongside the other creation parameters. |
serializeAsText |
A Boolean value that indicates whether the state information is persisted in XML or binary format. The default is False (indicating binary serialization). |
Service state data is persisted to the InstanceData table. The following table shows the fields in the InstanceData table.
Field | Description |
---|---|
id |
Unique identifier of the state instance. The instance ID is derived from the URL path of the resource. |
instanceType |
Type of instance being stored. |
instance |
Instance state persisted as binary data. Is null if the instance is persisted as text. |
instanceXml |
Instance state persisted as XML. Is null if the instance is persisted as binary data. |
created |
UTC time when the instance was first written to the database. |
lastUpdated |
UTC time the instance was last written to the database. |
lockOwner |
The host ID of the host that currently owns the lock on the record, or null if there is no active lock. |
lockExpiration |
The UTC time that the lock expires. |
Note
The instance ID is not identical to the session ID. The instance ID is a long-lived application layer identifier that identifies a public resource name. The session ID, on the other hand, is a transient transport layer identifier that identifies a private context token. This means that if the same resource is accessed multiple times, a new session ID is generated each time, but the same instance ID is used. In addition, instance IDs have a 1:many (Service:Client) relationship, but session IDs have a 1:1 (Service:Client) relationship.
The SqlPersistenceProviderFactory can be configured in code using the class’s constructor, but it is usually preferable to configure it (and service providers in general) using an application configuration file. A sample configuration is as follows. (Note that the format for durable service configuration files is the same as for Windows Communication Foundation (WCF) services.)
<configuration>
<connectionStrings>
<add name="LocalConnection"
connectionString="
Initial Catalog=SqlPersistence;
Data Source=localhost;
Integrated Security=SSPI;"
/>
</connectionStrings> <system.serviceModel>
<services>
<service
name="SampleService"
behaviorConfiguration="ServiceBehavior">
<endpoint
address="ISampleService_Basic"
binding="basicHttpContextBinding"
contract="SampleApplication.ISampleService"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<persistenceProvider
persistenceOperationTimeout="00:00:15"
type="
System.ServiceModel.Persistence.SqlPersistenceProviderFactory,
System.WorkflowServices,
Version=3.5.0.0",
Culture=neutral,
PublicKeyToken=31bf3856ad364e35"
connectionStringName="LocalConnection",
lockTimeout="00:10:00"
/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Configuring Durable Services
WCF service implemented in code that is persisted to a storage medium, typically a SQL database. The service can then be reloaded so that a client can connect to a specific service at any time.
A durable service uses the same object model as a workflow service, and uses the same underlying context protocol channel mechanism, so that messages can be routed to the correct instance of a service.
The difference between a durable service written in code, and one written as a workflow, is that the workflow can get persisted in between an operation invocation, while a durable service is persisted after the operation invocation is completed.
To mark a service implementation as durable, mark the service using the DurableServiceAttribute. The following table shows the available parameter for this attribute.
Parameter | Description |
---|---|
SaveStateInOperationTransaction |
Explicitly makes all instance states transactional. This flag verifies that ConcurrencyMode is Single, and that all operations will run under a transaction. (TransactionScopeRequired must be set to True, or TransactionFlowOption must be set.) |
When the attribute is present on a service, the ApplyDispatchBehavior method will configure the necessary DispatchRuntime components to correctly extract an instance ID from a message, route it to the correct instance, and perform persistence operations around invocation.
Operations on durable services should be marked with DurableOperationAttribute. The following table shows the property for this attribute.
Parameter | Description |
---|---|
CompletesInstance |
Specifies that the instance is completed (and will be persisted and unloaded from memory) after this operation is finished. The default is False. |
The following code sample demonstrates creating a durable service.
[ServiceContract()]
public interface IService1
{
[OperationContract]
string MyOperation1(string myValue);
[OperationContract]
string MyOperation2(DataContract1 dataContractValue);
}
[DurableService(MyPersistenceProvider)]
public class service1 : IService1
{
[DurableOperation]
public string MyOperation1(string myValue)
{
Return "Hello: " + myValue;
}
[DurableOperation]
string MyOperation2(DataContract1 dataContractValue)
{
return "Hello: " + dataContractValue.DataName;
}
}
[DataContract]
public class DataContract1
{
string dataNameValue;
[DataMember]
public string DataName
{
get { return dataNameValue; }
set { dataNameValue = value; }
}
}
See Also
Other Resources
Tutorial: Create a Durable Service
Creating Workflow Services and Durable Services