Thursday, March 08, 2012

Proxy-Free WCF: Bonus Round:Entity Framework

In a few previous posts, we've seen how to go about eliminating the auto-generated service references from your WCF clients. This is done by using a ChannelFactory and sharing your model objects through a channel independent of the WCF service itself (e.g. a shared assembly.)

In those posts, our model was a very simple, single POCO entity that we moved into a class library, which we then shared between client and server. In practice, your model is probably going to be significantly more complex. It might be something like an Entity Framework model, which we certainly don't want to share in its entirety with our client!

Today, we'll look at how the Entity Framework already supports being used as a data contract for WCF, and how we can extend this to support our proxy-free WCF clients.

UPDATE: See this blog post for the sample code for this article.


Entity Framework Data Contracts

Out of the box, the Entity Framework produces objects that can be used as-is in a WCF service. To see how this works, lets change our previous project to have a more interesting data model. I'm going to reuse a model I built for a recent stackoverflow question I answered, which looks like this:
If we look at the code generated by the built-in EF template, we see that each of our entities has been defined like this (additional fields and constructors omitted for brevity):
[EdmEntityTypeAttribute(NamespaceName="ProxylessWcfModel", Name="Genre")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Genre : EntityObject
{
    [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
    [DataMemberAttribute()]
    public global::System.Int32 Id
    {
        get
        {
            return _Id;
        }
        set
        {
            if (_Id != value)
            {
                OnIdChanging(value);
                ReportPropertyChanging("Id");
                _Id = StructuralObject.SetValidValue(value);
                ReportPropertyChanged("Id");
                OnIdChanged();
            }
        }
    }
    private global::System.Int32 _Id;
    partial void OnIdChanging(global::System.Int32 value);
    partial void OnIdChanged();
    
    [XmlIgnoreAttribute()]
    [SoapIgnoreAttribute()]
    [DataMemberAttribute()]
    [EdmRelationshipNavigationPropertyAttribute("ProxylessWcfModel", "FK_Book_Genre", "Book")]
    public EntityCollection Books
    {
        get
        {
            return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection("ProxylessWcfModel.FK_Book_Genre", "Book");
        }
        set
        {
            if ((value != null))
            {
                ((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection("ProxylessWcfModel.FK_Book_Genre", "Book", value);
            }
        }
    }
}
All those attributes on the type and its fields mean that this object is set up and ready to be serialized using WCF's DataContractSerializer. We can then use these entities in our new IProxyless interface, which has been redefined as:
[ServiceContract(Namespace = "http://namespaces.kutulu.org/wcf/proxyless")]
public interface IProxyless
{
    [OperationContract]
    bool IsVersionOk(string version);

    [OperationContract]
    string GetConfigurationElement(string key);

    [OperationContract]
    ObservableCollection<Book> GetAllBooks();
        
    [OperationContract]
    ObservableCollection<Author> GetAllAuthors();

    [OperationContract]
    ObservableCollection<Genre> GetAllGenres();

    [OperationContract]
    void SaveBook(Book book);
}
If we were to extract the WSDL for this service contract and generate a proxy class from it, you would see that our data contract entities have all been redefined on the client, albeit not in exactly the same structure:
public partial class Genre : proxylessclient.ServiceReference1.EntityObject {
    
    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private System.Collections.ObjectModel.ObservableCollection<proxylessclient.ServiceReference1.Book> BooksField;
    
    [System.Runtime.Serialization.OptionalFieldAttribute()]
    private int IdField;
      
    [System.Runtime.Serialization.DataMemberAttribute()]
    public System.Collections.ObjectModel.ObservableCollection<proxylessclient.ServiceReference1.Book> Books {
        get {
            return this.BooksField;
        }
        set {
            if ((object.ReferenceEquals(this.BooksField, value) != true)) {
                this.BooksField = value;
                this.RaisePropertyChanged("Books");
            }
        }
    }
    
    [System.Runtime.Serialization.DataMemberAttribute()]
    public int Id {
        get {
            return this.IdField;
        }
        set {
            if ((this.IdField.Equals(value) != true)) {
                this.IdField = value;
                this.RaisePropertyChanged("Id");
            }
        }
    }
}
To generate this class, I had to explicitly specify that I wanted my collections to be ObservableCollections, as we've seen previously in the Service Reference Advanced Options. By default, they would be generated as simple arrays on the client side.

Unfortunately, there are a few problems with these entities, as currently implemented. The two biggest issues are:
  1. Everything is defined in a single file, making it impossible to share only the data contracts with potential client applications
  2. Change tracking only happens when a client is attached to a context, and is lost otherwise.

Self-Tracking Enties

That second problem is actually going to be an issue no matter how we choose to implement our entity model. The entity objects produced by the EF's standard template do all of their change tracking via the attached context: only the context knows which objects have been added, removed, edited, etc. during its lifetime. This information is not serialized with the entity (its not part of the data contract). When we attach an object coming in from a client, which we have to do to save it, the change information is lost. This is not an insurmountable problem, but the solution is to manually set the change state for each field on the object that needs to be saved into the database.

I bring this up not because it directly related to sharing data contracts, but because the solution to this second problem conveniently solves the first problem for us as well: we use self-tracking entities. In Visual Studio 2010, with the Entity Framework 4, Microsoft now directly supports the ability to swap out the default code generation template for a new one. There are a number of custom templates (you can get a POCO one in the Visual Studio Gallery) but the one we're interested in now actually ships with Studio. First, we right-click in our model diagram and Add a Code Generation Item. From the list of Code options, pick the Self-Tracking Entities template and add that to our diagram:



This adds two new files to our project: ProxylessModel.tt, and ProxylessModel.Context.tt. If you drill down beneath these templates, you will see that ProxylessModel.Context.tt generates a context class (and some handy extension methods), while ProxylessModel.tt generates a separate code file for each entity in our model, plus a utility class for change tracking.

Score! We've now solved two major problems with one simple right click! Well, maybe a few more. We also need to edit the properties on the existing ProxylessModel.emdx and remove the Custom Tool (set it to an empty string); if we don't, we'll get duplicate classes. But after that, we're 90% of the way to where we need to be.

If we look at the ProxylessModel.cs file generated by the new template, you will see a new data contract entity that isn't part of our model diagram, with some interesting data members:
[DataContract(IsReference = true)]
public class ObjectChangeTracker
{  
    [DataMember]
    public ObjectState State[...]

    [DataMember]
    public ObjectsRemovedFromCollectionProperties ObjectsRemovedFromCollectionProperties[...]

    [DataMember]
    public OriginalValuesDictionary OriginalValues[...]
}

public interface IObjectWithChangeTracker
{
    // Has all the change tracking information for the subgraph of a given object.
    ObjectChangeTracker ChangeTracker { get; }
}
All of the entities generated by this template implement the IObjectWithChangeTracker interface, so they all have a public change tracker built in. The state change information, including adds, edits, and deletes, are serialized from client to server along with the custom data fields in our entities. This information is used on the server side by "applying" it to an entity set using one of those handy extension methods I mentioned earlier. For example, our service could implement a simple Save method like so:
public Book SaveBook(Book book)
{
    using (var context = new ProxylessWcfEntities())
    {
        context.Books.ApplyChanges(book);
        context.SaveChanges();

        book.AcceptChanges();
        return book;
    }
}
Notice that we return the object after accepting the saved changes; this gives the client back an object that is in the correct state to be edited further, without causing concurrency violations (e.g. trying to be added twice.)

The final step in getting our client access to these super smart new entities is pretty simple. It does involve editing the templates, but T4 is straightforward enough; just pretend it's ASP with funny tags. First, we move the ProxylessModel.tt file into the shared assembly project and delete the one from the service project. Next, edit the T4 code for ProxylessModel.tt, and near the top, change the reference path to the source model diagram:
// Old T4 Code:
string inputFile = @"ProxylessModel.edmx";
// New T4 Code:
string inputFile = @"..\ProxylessService\ProxylessModel.edmx";
This will produce the same entity classes, this time in your shared library, based on the entity model in the service project. It should happen automatically when to save your entity model diagram, but that doesn't always work cross-project. If so, just right-click on the .tt file and Run Custom Tool to generate them manually.

Additionally, we have to make a change to the ProxylessModel.Context.tt, since some of our classes are now in a different namespace. Near the top of that file, find the call to WriteHeader and add our custom namespace to it, like so:
// Old T4 Code:
WriteHeader(fileManager);

// New T4 Code:
WriteHeader(fileManager, "KutuluWare.ProxylessServiceLibrary");
Once this is done, both our client and server have direct access to the self-tracking entity objects, including the extension methods and internal state tracking data. We can now use these entities on our client, the same as our POCO entities from last time. Notice that every service call returns an object, even if it's just a copy of the one we passed in. Again, this is crucial to get right, because it keeps the change tracking state properly synchronized between client and server:
var client = ServiceFactory.CreateChannel<IProxylessChannel>(url);
var authors = client.GetAllAuthors();
var genres = client.GetAllGenres();

var book = new Book
{
    Title = "New Book Title",
    Genre = genres.Single(x => x.Name.Equals("Horror")),
};
book.Authors.Add(authors.Single(x => x.Name.Equals("King, Stephen")));
book = client.SaveBook(book);

More To Come

This wraps up our look at proxyless WCF clients, and how to use them with real-world architectures. However, we've now opened up Pandora's box of cool new things, by digging in to the EF templates and making our own modifications.

In upcoming posts, we'll see some of the other interesting things you can do just by tweaking these templates; including a way to get Visual Studio to generate a repository pattern for you automatically, each time you change your model. Stay tuned.

0 comments: