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

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Mike Taulty's Blog said:

I've been offline for a little while so I did a bit of blog catching up yesterday and this is the set...

May 23, 2007 1:13 AM
 

Thomas said:

With this approach you loose a lot of convenience I am expecting from LINQ. I'd like to have my objects, changing them and then just writing them back to the database. LINQ should handle itselve if it should use a update, insert or delete. And I don't want to assign the properties manually because what if you forget to add the code for a new property here?

You should better use the Attach-Method which would lead to less problems but is still not optimal. i hope they improve this until the final.

September 17, 2007 8:21 AM
 

jwooley said:

Thomas, I would agree that it would be nice if the Context were smarter when attaching objects to determine if it is an update or insert. Delete naturally would require the user to specify this action. Unfortunately, the Attach method does not in itself check for property changes. It just wires up the change tracking to the object against which subsequent changes are logged. There is a small change with attach for RTM planned, but I don't expect major overhauls at this point.

September 17, 2007 8:31 AM
 

Richard said:

The link to the following page in this article does not work

http://linqinaction.net/photos/jwooley/picture44239.aspx

October 9, 2007 2:51 AM
 

admin said:

Richard, this has been fixed. It came from the cross-posting.

The original post can be found here: http://devauthority.com/blogs/jwooley/archive/2007/05/14/44266.aspx

October 9, 2007 8:55 AM
 

hobbes said:

for the case of function 'UpdatePost', what if the data has already been updated by another user before you do the update?

November 19, 2007 1:44 AM
 

REDBYRON said:

hobbes,

I think it depends on your data concurrency options...

if you use timestamp fields or check original values of entity for

the updte to take place...

December 1, 2007 7:45 AM
 

jwooley said:

There are a number of options you have regarding concurrency. Indeed using a timestamp if you are able to include it in the database schema is optimal. I tried to emphasise this in chapter 8.

December 2, 2007 8:51 PM
 

AWL said:

I'm using the full release version of vs2008 professional right now. I don't see Custom Attributes in the list of properties. I see everything else. Did they take it out before release?

December 20, 2007 1:36 PM
 

jwooley said:

AWL,

Unfortunately the Custom Attributes were dropped in Beta 2 and did not return on the released version. As an alterntive, you can set the Serialization Mode of the DataContext to "UniDirectional" and the appropriate WCF attributes will be added for you. This is fine for WCF, but doesn't allow for the added flexibility that manually setting the attributes gave for other scenarios.

December 20, 2007 1:50 PM
 

Patrick said:

This solution seems to have a security problem... what if the foreign component changed the identity (id) field? You could wind up writing data over a completely different record... or over every record if someone felt like being malicious (though I would be less worried about maliciousness here, than about lazy developers down the chain doing something dumb).

Perhaps you could compare a RowVersion property?

March 25, 2008 6:20 PM
 

jwooley said:

Patrik,

I would agree that allowing the user to supply the identity could be a security concern. This is the problem with much of the samples you see in that the service does not re-validate the information being sent. It does need to be validated. This post was specifically made in regards to exposing the entities through WCF. The additional complexities were omitted to isolate the WCF requirements.

I also agree that a RowVersion would be benificial not only for validation. It is also critical for concurrency checking in a distributed implementation to avoid having to pass a copy of the original object as well.

March 26, 2008 6:33 AM
 

Daryl said:

Look into "LINQ to ADO.NET Data Services":

http://quickstarts.asp.net/3-5-extensions/adonetdataservice/NETClientLibrary.aspx

This seems like the easiest way to expose CRUD operations to internet apps.  It allows for "hooks" which can decide what permissions (if any) a user has on a particular row.

This is the artist formerly known as "Project Astoria"

March 27, 2008 12:44 PM
 

jwooley said:

Daryl,

I would agree that LINQ to ADO.Net Data Services offers an intreguing alternative to SOAP based services (ASMX/WCF). At the time that this post was made, Astoria had not been made public. In addition, there are times where WCF will be needed as compared to the RESTful implementation that Astoria offers.

March 27, 2008 2:14 PM
 

John Leitch said:

I wrote a tool that automates a lot of this.

http://wcfmetal.codeplex.com/

February 9, 2010 1:42 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit