Linq to SQL with WCF Services

I was interested to see how I could use Linq to SQL with WCF Services to load and save data using a Silverlight project.  In this post I will expand upon the database I created in my Linq to SQL Tutorial and the console application I wrote for my Set inheritance modifiers with SQLMetal post.

The first step is to enable serialisation on my Linq entities so that they can be sent over the wire. To do this in the O/R Designer you can select the white space of the designer and view the DataContext properties.  Set the property called Serialization Mode to Unidirectional:

Linq Serialization Mode Property

 

If using SQLMetal you can use the serialization command line argument:

SQLMetal.exe"/server:localhost /database:University /dbml:University.dbml <strong>/serialization:unidirectional</strong> /namespace:Entities /context:UniversityDataContext /pluralize

Enabling unidirectional serialization in either of these two ways adds the necessary DataContract and DataMember attributes to the generated entities and properties:

[Table(Name="dbo.Student")]
[DataContract()]
public partial class Student : EntityBase, INotifyPropertyChanging, INotifyPropertyChanged
{
    ....

    [Column(Storage="_Forename", DbType="NVarChar(50) NOT NULL", CanBeNull=false, UpdateCheck=UpdateCheck.Never)]
    [DataMember(Order=3)]
    public string Forename
    {
        ....
    }
}

The entities are now in a state where they can be serialised and sent down the wire.  In my WCF service I have a method that returns a list of my Linq to SQL Student entity:

public List GetStudents()
{
    using (_context)
    {
        return _context.Students.ToList();
    }
}

These entities can then be easily used by the client, in this case the Silverlight application:


UniversityContractClient _proxy = new UniversityContractClient();

private void PopulateStudents()
{
    _proxy.GetStudentsCompleted += new EventHandler(proxy_GetStudentsCompleted);
    _proxy.GetStudentsAsync();
}

void proxy_GetStudentsCompleted(object sender, GetStudentsCompletedEventArgs e)
{
    dgStudents.ItemsSource = e.Result;
}

Here I am using the list to populate a DataGrid:

Linq WCF Datagrid

This is all very straight forward, but the next step to update the data it a little more complex.  Here is my service method to save a Student entity created or updated by the client:

public void SaveStudent(Student student)
{
    using (_context)
    {
        if (student.IsNew)
        {
            _context.Students.InsertOnSubmit(student);
        }
        else
        {
            _context.Students.Attach(student, true);
        }
               
        _context.SubmitChanges();
    }
}

Here I am using the IsNew property I created in my Set inheritance modifiers with SQLMetal post to check if the entity is to be inserted or updated.  The insert code is simple enough, but for the update we have to attach the entity to the DataContext as it has been modified outside of the DataContext’s scope.  I’m at doing this using the Attach method of the Student table, passing true for the asModified parameter to state that the entity has been updated.

In my Silverlight application I have a DataForm which calls this method passing the updated Student entity:

Linq WCF DataForm

At this point inserting data will work, but when I try to update an entity the service method will throw the following error when trying to attach the entity:

An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.

This occurs because the entity was modified outside of the scope of the DataContext, so Linq to SQL doesn’t know what has changed about the entity and what to update.  To overcome this we can use a Timestamp column.  The Timestamp is a byte array which is used for versioning.  Linq to SQL knows to check this column to see if an object has been updated.  In my database I have changed the Student table so that it has a field called Timestamp, of type timestamp which doesn’t allow NULLs:

Linq WCF Timestamp Field

When adding the new column, the O/R Designer automatically knows this is a timetamp column and sets the Time Stamp and Auto Generated Value properties to true:

Linq WCF Timestamp Properties

 

SQLMetal will also detect a column with the timestamp type and set the necessary attributes.

With this timestamp column set up it will now be possible to successfully update an entity that was changed by the client.

In my example if I try to update the entity twice it will throw the following exception when trying to submit the changes:

Row not found or changed.

This is because the client doesn’t have the entity with the updated timestamp.  Also when adding a new entity the entity at the client won’t have the updated ID identity column so trying to update this will create another entity.  To resolve this I can change my SaveStudent service method to return the updated Student entity:


public Student SaveStudent(Student student)
{
    using (_context)
    {
        if (student.IsNew)
        {
            _context.Students.InsertOnSubmit(student);
        }
        else
        {
            _context.Students.Attach(student, true);
        }
               
        _context.SubmitChanges();
    }

    return student;
}

In my Silverlight application I then pass the hash code for the object as the userState when calling the asyncronus service method:

_proxy.SaveStudentAsync(student, student.GetHashCode());

This user state can then be obtained in the callback EventArgs class using e.UserState.  Using this I get the correct object from my collection, update it and reassign the source for my DataGrid and DataForm:

void _proxy_SaveStudentCompleted(object sender, SaveStudentCompletedEventArgs e)
{
    ObservableCollection students = (ObservableCollection)dgStudents.ItemsSource;
    Student student = students.Where(s => s.GetHashCode() == Convert.ToInt32(e.UserState)).First();
    if (student.ID == 0)
    {
        student.ID = e.Result.ID;
    }
    student.Timestamp = e.Result.Timestamp;
    dgStudents.ItemsSource = students;
    dfStudent.ItemsSource = students;
}

This is all well and good and works as expected but what I really wanted to do was have an UpdateDate column which holds the date of the last update which could be used as a timestamp.  I replaced my current Timestamp column with an UpdateDate column:

Linq WCF UpdateDate Field

The default for the new column is set to getdate() to automatically populate with the current date when creating a new record:

Linq WCF UpdateDate Default

Using the O/R Designer this field can be set to a timestamp by setting the Time Stamp property to True, which will automatically set Auto Generated Value to True.

As I am using SQLMetal I can update the console application I wrote in my Set inheritance modifiers with SQLMetal post to add an IsVersion attribute to the DBML XML as well as the Modifier attribute:


.... code omitted ....

//Find the column node
if (child.Name.Equals("Column"))
{
    if (child.Attributes["Name"].Value.Equals("ID"))
    {
        //Create the Modifier attribute to add to ID column
        XmlAttribute modifierAttribute = xmlDoc.CreateAttribute("Modifier");
        modifierAttribute.Value = "Override";
        child.Attributes.Append(modifierAttribute);
    }
    else if (child.Attributes["Name"].Value.Equals("UpdateDate"))
    {
        //Create the IsVersion attribute to add to UpdateDate column
        XmlAttribute versionAttribute = xmlDoc.CreateAttribute("IsVersion");
        versionAttribute.Value = "True";
        child.Attributes.Append(versionAttribute);
    }
}

.... code omitted ....

Doing this adds the following values to the Column attribute on the UpdateDate property in the Student entity.  You can see IsVersion=true which tells Linq to SQL this property is the timestamp.

[Column(Storage="_UpdateDate", AutoSync=AutoSync.Always, DbType="DateTime NOT NULL", IsDbGenerated=true, IsVersion=true, UpdateCheck=UpdateCheck.Never)]

At this point everything works okay, but the UpdateDate is not refreshed on update.  To fix this add a trigger that sets the date on update:

ALTER TRIGGER trg_UpdateDate
ON dbo.Student
FOR UPDATE
AS
    UPDATE      Student
    SET         UpdateDate = getdate()
    WHERE       (ID IN (SELECT ID FROM Inserted))

The UpdateDate is now set for each update and is used by Linq to SQL as the timestamp.

Posted on by Joe in Linq, Silverlight, WCF

5 Responses to Linq to SQL with WCF Services

  1. Steve

    Hi , good post, thanks.

    How are you managing your datacontext with WCF ?

    Also, how would you handle using a DTO rather than passing the Entity to the client ?

    Thanks

  2. Joe

    Hey Steve

    For simplicity in this example I’m just creating an instance of the DataContext in the service constructor. Normally I’d probably have some repository classes to manipulate the data and have a DataContext instance for each class that lives for the lifetime of the class itself. I can’t really think of a better way to do it…

    Not sure about DTO as only really started looking into this stuff myself. I guess you could have some sort of converter at the service level that converts the the Linq to SQL entities to DTOs and vice versa, although without looking into it further I’m not sure how I’d handle the DataContext in this situation.

    Cheers
    Joe

  3. John Leitch

    I created a utility that generates a WCF data access service based on LINQ-to-SQL generated classes.
    It can be found at http://wcfmetal.codeplex.com/

  4. Rakesh Tiwari

    I like aal of your posts

  5. Pingback: Linq to SQL with WCF Services « Sochinda's Blog

Add a Comment