Managing the Data Service Context (WCF Data Services)

The DataServiceContext class encapsulates operations that are supported against a specified data service. Although OData services are stateless, the context is not. Therefore, you can use the DataServiceContext class to maintain state on the client between interactions with the data service in order to support features such as change management. This class also manages identities and tracks changes.

Merge Options and Identity Resolution

When a DataServiceQuery<TElement> is executed, the entities in the response feed are materialized into objects. For more information, see Object Materialization (WCF Data Services). The way in which entries in a response message are materialized into objects is performed based on identity resolution and depends on the merge option under which the query was executed. When multiple queries or load requests are executed in the scope of a single DataServiceContext, the WCF Data Services client only tracks a single instance of an object that has a specific key value. This key, which is used to perform identity resolution, uniquely identifies an entity.

By default, the client only materializes an entry in the response feed into an object for entities that are not already being tracked by the DataServiceContext. This means that changes to objects already in the cache are not overwritten. This behavior is controlled by specifying a MergeOption value for queries and load operations. This option is specified by setting the MergeOption property on the DataServiceContext. The default merge option value is AppendOnly. This only materializes objects for entities that are not already being tracked, which means that existing objects are not overwritten. Another way to prevent changes to objects on the client from being overwritten by updates from the data service is to specify PreserveChanges. When you specify OverwriteChanges, values of objects on the client are replaced by the latest values from the entries in the response feed, even if changes have already been made to these objects. When a NoTracking merge option is used, the DataServiceContext cannot send changes made on client objects to the data service. With this option, changes are always overwritten with values from the data service.

Managing Concurrency

OData supports optimistic concurrency that enables the data service to detect update conflicts. The data service provider can be configured in such a way that the data service checks for changes to entities by using a concurrency token. This token includes one or more properties of an entity type that are validated by the data service to determine whether a resource has changed. Concurrency tokens, which are included in the eTag header of requests to and responses from the data service, are managed for you by the WCF Data Services client. For more information, see Updating the Data Service (WCF Data Services).

The DataServiceContext tracks changes made to objects that have been reported manually by using AddObject, UpdateObject, and DeleteObject, or by a DataServiceCollection<T>. When the SaveChanges method is called, the client sends changes back to the data service. SaveChanges can fail when data changes in the client conflict with changes in the data service. When this occurs, you must query for the entity resource again to receive the update data. To overwrite changes in the data service, execute the query using the PreserveChanges merge option. When you call SaveChanges again, the changes preserved on the client are persisted to the data service, as long as other changes have not already been made to the resource in the data service.

Saving Changes

Changes are tracked in the DataServiceContext instance but not sent to the server immediately. After you are finished with the required changes for a specified activity, call SaveChanges to submit all the changes to the data service. A DataServiceResponse object is returned after the SaveChanges operation is complete. The DataServiceResponse object includes a sequence of OperationResponse objects that, in turn, contain a sequence of EntityDescriptor or LinkDescriptor instances that represent the changes persisted or attempted. When an entity is created or modified in the data service, the EntityDescriptor includes a reference to the updated entity, including any server-generated property values, such as the generated ProductID value in the previous example. The client library automatically updates the .NET Framework object to have these new values.

For successful insert and update operations, the state property of the EntityDescriptor or LinkDescriptor object associated with the operation is set to Unchanged and the new values are merged by using OverwriteChanges.

When an insert, update, or delete operation fails in the data service, the entity state remains the same as it was before SaveChanges was called, and the Error property of the OperationResponse is set to an DataServiceRequestException that contains information about the error. For more information, see Updating the Data Service (WCF Data Services).

Managing Requests

The OData protocol provides flexibility in behaviors of requests to and responses from a data service. The client library enables you to leverage this flexibility by controlling how your application interacts with the data service.

Setting the Prefer Header for Change Operations

By default, a message payload is only returned in response to a POST request to create a new entity. In this case, the new entity is returned in the payload. This means that when updates are made to objects, the updated entity is not returned in the payload or the response message. However, the OData protocol states that a client may use the Prefer header to request a change from this default behavior. The Prefer header in an POST, PUT, PATCH, or MERGE request is generated by the DataServiceContext based on the value of the DataServiceResponsePreference set in the AddAndUpdateResponsePreference property. The following table shows the Prefer header options and the related response behaviors:

Prefer header value

AddAndUpdateResponsePreference value

Response behavior

Not included in the request. This is default behavior.

None

A response payload is only returned for POST requests and not for PUT, PATCH, and MERGE requests.

return-content

IncludeContent

A response payload is returned for all change requests.

return-no-content

NoContent

No response payload is returned for any requests. For a POST request, the data service also includes a DataServiceId header in the response. This header is used to communicate the key value of the newly created entity.

Note

Even when the data service supports a version of the OData protocol that supports the Prefer header, a data service can choose not to honor these kinds of request processing preferences.

Setting the HTTP Method for Updates

By default, the .NET Framework client library sends updates to existing entities as MERGE requests. In OData, a MERGE request updates selected properties of the entity; however the client always includes all properties in the MERGE request, even properties that have not changed. The OData protocol also supports sending PUT and PATCH requests to update entities. In a PUT request, an existing entity is essentially replaced with a new instance of the entity with property values from the client. PATCH requests are handled in the same way as MERGE requests; however PATCH is a standard HTTP action, whereas MERGE is defined by OData. This update behavior is specified by supplying either the ReplaceOnUpdate value, to use PUT requests, or the PatchOnUpdate value, to use PATCH requests, as options when calling SaveChanges(SaveChangesOptions).

Note

A PUT request will behave differently than a MERGE or PATCH request when the client does not know about all properties of the entity. This might occur when projecting an entity type into a new type on the client. It might also occur when new properties have been added to the entity in the service data model and the IgnoreMissingProperties property on the DataServiceContext is set to true to ignore such client mapping errors. In these cases, a PUT request will reset any properties that are unknown to the client to their default values.

POST Tunneling

By default, the client library sends create, read, update, and delete requests to an OData service by using the corresponding HTTP methods of POST, GET, PUT/MERGE/PATCH, and DELETE. This upholds the basic principles of Representational State Transfer (REST). However, not every Web server implementation supports the full set of HTTP methods. In some cases, the supported methods might be restricted to just GET and POST. This can happen when an intermediary, like a firewall, blocks requests with certain methods. Because the GET and POST methods are most often supported, OData prescribes a way to execute any unsupported HTTP methods by using a POST request. Known as method tunneling or POST tunneling, this enables a client to send a POST request with the actual method specified in the custom X-HTTP-Method header. To enable POST tunneling for requests, set the UsePostTunneling property on the DataServiceContext instance to true.

Resolving the Base URI of Entity Sets

By default, the client assumes that all entity sets, also known as collections, share the same base URI. This base URI is defined by the BaseUri property in the DataServiceContext. However, the OData protocol allows for a data service to expose entity sets as feeds that have different base URIs. To be able to handle entity sets with different base URIs, the client enables you to register a delegate with the DataServiceContext that can be used to resolve the base URIs of various entity sets. This delegate is a method that takes a string (the entity set name) and returns a Uri (the base URI for the specified entity set). This resolver is registered with the DataServiceContext by assignment to the ResolveEntitySet property. An example resolver implementation might, when the context is created, read the information about collections that is returned by the data service root and store the base URI values for each collection in a dictionary. This dictionary could then be used by the resolver to return the URI for a specific entity set. For a more complete example, including sample code, see the post Entity Set Resolver.

Versioning Requirements

The DataServiceContext behaviors have the following OData protocol versioning requirements:

  • Support for the Prefer header and PATCH requests require that both the client and data service support version 3.0 of the OData protocol and later versions.

  • Support for entity set resolvers requires the version of the WCF Data Services client library that is included with the Data Framework release, or a later version.

For more information, see Data Service Versioning (WCF Data Services).