Wooley's LINQ Wonderings

Insights and observations regarding LINQ

WCF with the LINQ to SQL designer

A frequent question on the LINQ forums regards how to combine LINQ with a service oriented application. The main issue is that the DataContext which sits at the core of LINQ to SQL in managing the connection and change tracking is not intended to be remoted. In addition, the DataContext should be rather short lived in a disconnected environment (including ASP). With the addition of WCF in the 3.0 framework, the question of how to enable LINQ to SQL entity definitions to participate in a WCF DataContract is not unexpected.

If you are unfamiliar with WCF, the core of it relies on a simple concept of ABC, or Address, Binding and Contract. The Address is the location that hosts the service. This is typically a URI. The Binding indicates how we connect to the address (HTTP/TCP/etc). The Contract indicates what you are going to connect to. This represents a set of method calls that are accessible to the outside world.

The WCF ServiceContract can consist of methods and functions which take or return primitive types (strings, integers, etc). It can also accept or return complex object types (objects) as long as they are configured with the necessary System.Runtime.Serialization.DataContract and DataMember attributes.

LINQ to SQL also operates based on decorating classes with System.Data.Linq.Table and Column attributes to indicate the mappings (assuming you are not using the XML mapping mechanisms). If you add your attributes manually, you can add both of the necessary sets of attributes with no problem. If, on the other hand, you use the LINQ to SQL designer, managing the WCF attributes can be a bit trickier as you can't modify the class file natively without risking any of your custom code being removed as the classes are regenerated as changes are made to the designer. Thus any changes you make should only be done through the property window or directly with the XML in the .dbml file.

Today, we'll extend the ThinqLinq.com sample website that is available for download in the file section here. This sample is a proof of concept site that manages blog posts. For the purposes of this article, we will extend the support for a post to be able to fetch and update them using WCF services rather than native LINQ to SQL calls. To begin the WCF implementation, we will define our contract in an Interface as follows:

Imports System.ServiceModel
Imports LinqBlog.BO

<ServiceContract()> _
Public Interface IWcfItems

    <OperationContract()> _
    Function GetPosts(ByVal skipPosts As Integer, ByVal fetchPostCount As Integer) As PostItem()
    <OperationContract()> _
    Function GetSinglePost(ByVal id As Integer) As PostItem
    <OperationContract()> _
    Sub UpdatePost(ByVal item As PostItem)
    <OperationContract()> _
    Sub InsertPost(ByVal item As PostItem)
    <OperationContract()> _
    Sub DeletePost(ByVal item As PostItem)

End Interface

In this code, we set up a ServiceContract for the class definition. Each method is assigned an OperationContract to define the method signatures that will be available outside of our code. The function of each method should be self-evident from their names. What may not be apparent is the use of more complex types (the PostItem).

Since PostItem is an object, we need to decorate it with additional attributes to define how the WCF serializer will handle marshaling the object across the wire. At its simplest, our PostItem object consists of 5 properties: an auto-incrementing integer called Id, a Title, Author, PublicationDate and Description (which holds the content of the post). To enable the PostItem object to participate in a WCF method, we need to decorate the object with the System.Runtime.Serialization.DataContract and each of the properties with a System.Runtime.Serialization.DataMember attribute. The figure below shows the PostItem object in the LinqToSql designer in the Orcas Beta 1 release.

 

In this example, I'm showing not only the PostItem object, but also the relevant parts of the property window and Attribute window which we can use to declare the DataContract and DataMember attributes. To begin, select the PostItem class and locate the Custom Attributes property in the property window. Click on the ellipsis button to open the Attribute Editor window. Add the DataContext attribute by clicking the Add button and supplying the fully qualified name of the DataContext attribute as shown in the example. By using the property windows to maintain the attributes, they will be retained as the system generated class libraries are regenerated.

In addition to declaring the attribute for the class definition, we also need to declare them for the constituent properties as well. Follow the same process for each property of the PostItem object to set their CustomAttribute to DataMember. Remember to fully qualify the declaration.

At this point, our class should be set to participate in the WCF implementation. Below is the beginning of the implementation to fetch a list of PostItems:

Imports System.Data.Linq

Public Class WcfItems
    Implements IWcfItems

    Public Function GetPosts(ByVal skipPosts As Integer, ByVal fetchPostCount As Integer) As LinqBlog.BO.PostItem() Implements IWcfItems.GetPosts
        Dim dc As New LinqBlogDataContext
        Dim query = (From p In dc.PostItems _
                     Order By p.PublicationDate Descending).Skip(skipPosts).Take(fetchPostCount)
        Return query.ToArray()
    End Function

The GetPosts method takes as parameters the number of posts we are going to retrieve and the number that we will skip. It returns an array of PostItems. For friends of this blog, the internal implementation should be self explanatory as it uses a LINQ query to fetch post items ordered by the publication date. It also passes the input parameters using the .Skip and .Take methods to support paging options. Since we can't transmit the DataContext or the IQueryable definition over the wire, we need to immediately fetch the results ToArray and return them. Once we leave the method, the DataContext will be disposed.

Client applications can now consume our WCF method as it would any other service. Unlike the attached LINQ to SQL implementation, we need to manually manage the return values as the change tracking service is no longer attached to the context. Thus we need to take a bit more effort to support subsequent updates inserts and deletes.

Inserting values does not require the change tracking mechanisms. However, we can't just Add the object natively. Not shown in the above class definition, we have a child collection object of categories. In order to instantiate the collection,  We need to get a new object by  and then setting the appropriate values manually. We can then call the .Add method to add it to the table and SubmitChanges to commit the insert.

    Public Sub InsertPost(ByVal item As LinqBlog.BO.PostItem) Implements IWcfItems.InsertPost
        Dim dc As New LinqBlogDataContext
        'We need to map the returned type on a native new item in order to wire-up the child collections
        Dim newItem As New PostItem
        newItem.Author = item.Author
        newItem.Description = item.Description
        newItem.PublicationDate = item.PublicationDate
        newItem.Title = item.Title
        dc.PostItems.Add(newItem)
        dc.SubmitChanges()
    End Sub

To update an existing post item, we need to attach a returned post with the existing instance implementation. The standard recommendation is to use the Attach method to re-connect an existing record with the underlying object store. Unfortunately, the change tracking mechanism does not start until after the attach method is called. Thus, it does not know how to handle changes that were done remotely. As a result, we will re-fetch the instance from the database and then re-apply the changes from the returned PostItem from the service.

    Public Sub UpdatePost(ByVal item As LinqBlog.BO.PostItem) Implements IWcfItems.UpdatePost
        Dim dc As New LinqBlogDataContext
        Dim oldItem As PostItem = (From p In dc.PostItems Where p.Id = item.Id).FirstOrDefault()
        oldItem.Author = item.Author
        oldItem.Description = item.Description
        oldItem.PublicationDate = item.PublicationDate
        oldItem.Title = item.Title

        dc.SubmitChanges()
    End Sub

The remaining portion of the CRUD operation is the Delete. To delete an object from a table, we need to attach to an instance of the underlying object and then call the delete method on it as well. Here's a sample delete implementation:

    Public Sub DeletePost(ByVal item As LinqBlog.BO.PostItem) Implements IWcfItems.DeletePost
        Dim dc As New LinqBlogDataContext
        'Must attach before we can remove it
        Dim PostItem = (From p In dc.PostItems _
                Where p.Id = item.Id).FirstOrDefault()
        dc.PostItems.Remove(PostItem)
        dc.SubmitChanges()
    End Sub

Using LINQ to SQL in a disconnected environment such as WCF currently takes a bit more care and effort. The code in this post serves as a sample implementation. It is by no means the only possible implementation, but it hopefully shows that LINQ to SQL can have a positive impact on our application development. The most obvious enhancement is that we still don't need to worry about much of the ADO plumbing API's that we would have to include otherwise.

Crossposted from http://devauthority.com/blogs/jwooley/default.aspx
Published Monday, May 14, 2007 11:11 PM by jwooley
Anonymous comments are disabled