Feature CTP Walkthrough: Self Tracking Entities for the Entity Framework
Start
For the sake of brevity we are going to start with a prebuilt Visual Studio solution. The solution is a simple WCF service for Blogging, and contains three projects:
- BloggingModel: this is a class library containing an entity model created using the EDM designer.
- BloggingService: this is the WCF service application itself, contains the definition of the service contract and some code for the service.
- BloggingConsole: this is a simple console application structured as a series of simple tests for the service.
To start, open the EDM model named Blogging.edmx that is included in the BlogginModel project.
Adding Templates
The first step is going to be to add T4 templates for generating the self tracking entities. In order to do that, you will open the Blogging.edmx model contained in the BloggingModel project.
From any blank area in the surface of the EDM designer, right click and select the “Add New Artifact Generation Item…”
This will display a standard “Add New Item” dialog. Select the item type “EntityFramework SelfTracking code generator”, specify “Blogging.tt” as the name, and click on Add.
Note: As soon as the template files are added to the project, a security warning will pop up asking you to accept only if you trust the source of the template. Accept in order to continue with this walkthrough.
Notice that the BlogginModel project now contains two TT files: Blogging.Types.tt and Blogging.Context.tt.
What has just happened? When you add T4 templates for code generation like this, the default code generation used for Entity Framework models is turned off and the T4 templates take over that responsibility. T4 is a powerful code-generation tool included in Visual Studio. T4 templates can be modified by users to produce different code patterns based on the same input. To learn more about T4, you can visit this link.
Shared entity types
While self tracking entities code is generated automatically, the feature takes advantage of the new support for Persistence Ignorance in Entity Framework, and therefore the code generated is quite simple. You can browse the code files generated under Blogging.Types.tt to get an idea of what it involves.
As an example, the following code represents the public interface of the Person entity (not the complete source code):
[DataContract(IsReference = true)]
[KnownType(typeof(Entry))]
[KnownType(typeof(Blog))]
public partial class Person: IObjectWithChangeTracker
{
public Person();
[DataMember]
public int ID {get; set; }
[DataMember]
public string Firstname {get; set; }
[DataMember]
public string Surname {get; set; }
[DataMember]
public string EmailAddress {get; set; }
[DataMember]
public virtual FixupChangeTrackingCollection<Person, Entry> Entries {get; set; }
[DataMember]
public virtual FixupChangeTrackingCollection<Person, Blog> Blogs {get; set; }
public void MarkAsDeleted();
public void MarkAsAdded();
public void MarkAsModified();
public void MarkAsUnchanged();
[DataMember]
public ObjectChangeTracker ChangeTracker {get; set; }
}
A few things to observe in this code:
- The DataContract has IsRefernece = true: this allows WCF to serialize bidirectional graphs with cycles.
- All properties are marked as DataMember.
- FixupChangeTrackingCollection is a collection type that is also included in code generation that besides being able to store information about changes, provides the capability to align both sides of a relationship. For instance, when a Blog is added to a Person’s Blogs collection, the Blog’s Owner reference is also updated to point to the right Person.
- The methods called MarkAsDeleted, MarkAsModified, MarkAsAdded and MarkAsUnchanged can be used to manually control the state of individual entities. There are other, more implicit ways to establish the state of an entity: entities that are tracking are also automatically marked as Modified when a property value is modified. When adding a new entity to a collection in another tracking entity, the new entity will become Added.
- The ObjectChangeTracker property provides public access to the data structures used for change tracking.
The main goal behind designing self tracking entities as POCO classes is that they don’t require Entity Framework to be used on the client computer in distributed scenarios. In order to facilitate working with self tracking entities when they are serialized on the client, we are going to share the code for the types themselves between the service and the client.
Potentially, we could adapt self tracking entities to work with versions of the CLR that do not contain Entity Framework at all, such as Silverlight 3.
Note: The self tracking entity types generated by the T4 template included in the Entity Framework Feature CTP1 use attributes not present in the version of WCF included in Silverlight 2, like the OnDeserialized attribute. It is our goal to achieve compatibility with at least some version of Silverlight in the next release of the Feature CTP.
In order to get closer to this goal, as part of this walkthrough we are going to move the code generated for entity types (under Blogging.Types.tt) and the code that is part of the persistence layer (under Blogging.Context.tt) into separate assemblies.
As a first step, we are going to turn off code generation for the templates inside BloggingModel.
Select the Blogging.Types.tt file on the Solution Explorer. You should be able to see a property named “Custom Tool” in the Properties window (press F4 if the Properties window is not visible).
Remove the value from “Custom Tool” (“TextTemplatingFileGenerator”) and leave it blank. Also, notice that generated files (i.e. CS files) under this template are not removed automatically. You will need to expand the tree of generated files under the template and delete them.
Then, repeat the operation with Blogging.Context.tt: remove the Custom Tool value and manually delete all related files.
We are now going to need a new class library to accommodate the entity types. Right click on the solution and add a new class library project using Visual Studio and name it BloggingEntities.
Note: You can remove the default Class1.cs file that is automatically added to the project.
Next, we are going to add the template for types to the new BloggingEntities project. Right click on the BloggingEntities project and select “Add | Existing Item…”.
Browse for files in the BloggingModel project (you may need to go one level up to see it, and then specify All Files (*.*) as the filter).
Select Blogging.Types.tt in the dialog, and on the drop down button at the bottom of the dialog, select the option “Add As Link”.
When you add an item to a project as a link, the actual contents of the item are maintained in the location indicated by the original project (this is important because the TT file needs to be alongside the EDMX file in order to take it as input), but contribute generated code files to the current project. Since the link is specified using relative paths, the position is preserved if you move the solution to another directory or in source control.
If you try compiling the BloggingEntities project now, you will see a big number of errors. The reason is that self tracking entities use DataContract attributes defined in the WCF libraries, which are not referenced by default. Add a reference to the System.Runtime.Serialization library as shown below:
Service project
The initial version of the service project contains the service interface definition and basic code for the service implementation. We will be completing and removing commented lines from different parts of the source code until we have a functional WCF service.
The three first things the service is going to need are:
- The EDM metadata for the Blogging model.
- The CLR types for the entities.
- The code for the persistence layer (for the sake of brevity we are not going to create a separate assembly for the persistence layer, nor a full blown Repository abstraction).
Metadata
Since the BloggingModel project contains the EDMX file, the output assembly will contain the necessary metadata as embedded resources. In order to make the metadata available to the service, we can simply add a project reference on BloggingService pointing to BloggingModel.
For the Entity Framework runtime to find the metadata, it is necessary to provide the location (in this case a path that points to the embedded resources) in the connection string. An appropriate connection string is already included in the service’s web.config file:
<connectionStrings>
<add name="BloggingContainer" connectionString=
"metadata=res://*/Blogging.csdl|res://*/Blogging.ssdl|res://*/Blogging.msl; provider=System.Data.SqlClient;provider connection string="
Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Blogging.mdf;
Integrated Security=True;User Instance=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
</connectionStrings>
Entity Types
As explained previously, we are going to share the entity types between the service and the client. We do that by adding a reference to the BloggingEntities model.
Persistence layer
The persistence layer is going to be composed of the ObjectContext type generated by the Blogging.Context.tt file. Add a link to Blogging.Context.tt on the BloggingService project following the same procedure we used previously to add a link to the Blogging.Types.tt template to the BloggingEntities project.
As before, remember to add it as a link:
Important: Also, in order to get the namespace of the code generated by this template to match the namespace of the code generated by the Blogging.Types.tt template, we will need to set the value of the “Custom Tool Namespace” to “BloggingEntities”.
Service interface
The service interface is contained in the IBlogging.cs file. The interface defines four methods:
- Post GetPostWithComments(int PostID): retrieves a Post object by ID, including all the Comments related to that post.
- Void UpdatePostWithComments(Post post): provides the capability of persisting changes to a particular post, including a collection of its related Comment objects.
- GetATestPostID: retrieves an arbitrary PostID existing in the database for testing purposes.
- GetATestPersonID: retrieves an arbitrary PersonID from the database for testing purposes.
To continue with the walkthrough, un-comment the code contained in IBlogging.cs.
Service Implementation
The service implementation is contained in the Blogging.svc.cs file. GetPostWithComments, GetATestPostID and GetATestPersonID are just regular methods that return results of a query through the service operations. The only thing new about these methods with respect to something you could do previously with Entity Framework is that they now return graphs of self-tracking entities.
We are now going to focus on the most interesting operation, UpdatePostWithComments, which leverages self tracking entities in order to persist changes that have happened to the object graph on the client.
In order to proceed, uncomment the four public methods of the Blogging.svc file.
UpdatePostWithComments
This method takes a post self tracking entity, passes it through to validation logic, and then applies the changes contained in the object graph to the ObjectContext. Finally, it tries to save the changes.
public void UpdatePostWithComments(Post post)
{
ValidateUpdate(post);
ApplyDefaults(post);
using (var context = new BloggingContainer())
{
context.Entries.ApplyChanges(
post,
EntityChangeTrackerAdapter.GetSelfTrackingEntityInfo);
context.SaveChanges();
}
}
At the core of UpdatePostWithComments, we are using the ApplyChanges extension method that the Feature CTP components define on ObjectSet. The goal of ApplyChanges is to retrieve changes made to the graph of self tracking entities and infer, and reproduce all those changes on a regular ObjectContext and ObjectStateManager, so that the Entity Framework update components can store those changes to the database.
The method takes two parameters:
- The root object of the graph of self tracking entities
- A delegate type that takes an entity and returns an instance of the IEntityChangeTracker interface.
The delegate passed to the ApplyChanges method is an adapter that takes self tracking entities and returns a data structure that ApplyChanges can understand. The reason we define such an adapter interface is that we wanted self tracking entities to not have a dependency on Entity Framework.
EntityChangeTrackerAdapter.GetSelfTrackingEntityInfo simply returns an implementation of this adapter that is tailored for the entity types generated by our T4 template.
You would need to provide an adapter that complies with the same interface if, for instance, you wanted to write your own blend of self tracking entities that work with ApplyChanges.
Validation and Defaults
When creating a public facing service that takes client inputs, you should always validate those inputs. For that reason, the sample in this walkthrough demonstrates how to perform some basic validation that restricts the kind of changes that the self tracking entities can perform.
In order to enable this, uncomment the ValidateUpdate method.
private static void ValidateUpdate(Post post)
{
ValidateCondition(
post.ChangeTracker.State != ObjectState.Deleted,
"Deleting a post is not allowed");
ValidateCondition(
post.ChangeTracker.State != ObjectState.Added,
"Adding a post is not allowed");
ValidateCondition(
post.ChangeTracker.State == ObjectState.Unchanged ||
!string.IsNullOrEmpty(post.Body),
"Empty posts are not allowed");
ValidateCondition(
post.ChangeTracker.State == ObjectState.Unchanged ||
!string.IsNullOrEmpty(post.Title) ,
"Empty posts titles are not allowed");
ValidateCondition(
!post.Comments.Any(c => c.ChangeTracker.State != ObjectState.Unchanged &&
string.IsNullOrEmpty(c.Body)),
"Empty comments are not allowed");
ValidateCondition(
!post.Comments.Any(c => c.ChangeTracker.State != ObjectState.Unchanged &&
string.IsNullOrEmpty(c.Title) ),
"Empty comment titles are not allowed");
}
Essentially, this method throws an exception if any of the following conditions occur:
- The blog post contains an empty body or title.
- Any of the comments contain an empty body or title.
- The client is trying to add a new post or delete an existing post (the UpdatePostWithComments operation was only designed to perform updates on posts).
The next method invoked by UpdatePostWithComments is ApplyDefaults, which sets the ModifiedDate and CreatedDate on Posts and Comments as appropriate. This kind of method is useful if you expect client code not to do that part of the work.
private void ApplyDefaults(Post post)
{
var now = DateTime.Now;
post.ModifiedDate = now;
foreach (var comment in post.Comments)
{
switch (comment.ChangeTracker.State)
{
case ObjectState.Added:
comment.CreatedDate = now;
comment.ModifiedDate = now;
break;
case ObjectState.Modified:
comment.ModifiedDate = now;
break;
}
}
}
The Client
Included in the solution, there is a very simple console application that is used to test operations on the service. The BloggingConsole project will need two things to operate:
1. CLR types for entities.
2. A service reference to BloggingService.
It is worth mentioning that the client doesn’t need EDM metadata.
Entity types
The same way we did it on BloggingService, we can just add a project reference to BloggingEntities in order to get the definition of the types on the client.
Service Reference
Before you can actually add the service reference, you will need to make sure the service project is built. Otherwise, WCF design-time tools will fail to obtain the service’s metadata. Right click on the BloggingService project entry in the Solution explorer and choose the Build menu item.
Once built, you can proceed to add a standard Service Reference to BloggingService on the BloggingConsole project:
When configuring the service reference, specify BloggingService as the namespace. You will need to use the Discover button to find the service exposed by the BloggingService in the same solution.
Since the types of the entities are already known, WCF design-time tools will only add the minimum code necessary for the service interface and for the service client.
Client tests
In order to proceed, uncomment the methods on Progam.cs, which contains some simple scenario test for the service. The program follows this sequence:
- An existing PostID and a PersonID are obtained from the service.
- Retrieve the post corresponding with PostID.
- Add a new comment to the post, and save it through the UpdatePostWithComments service operation. The author for the comment is the person corresponding to PersonID.
- Delete all comments for the post and persist this change through the UpdatePostWithComments.
- Also, the post is retrieved and displayed in the console at various stages.
Notice that each separate method creates and disposes its own instance of the service client, just for the purpose of testing operations independently.
The most interesting method here is AddComment:
private static void AddComment(int postID, int authorID, string title, string body)
{
using (var service = new BloggingClient())
{
var post = service.GetPostWithComments(postID);
var author = new Person { ID = authorID };
post.Comments.Add(new Comment { Author = author, Title = title, Body = body });
service.UpdatePostWithComments(post);
}
}
The method follows this sequence:
- Creates an instance of the service client within a using statement.
- Retrieves a blog post from the service using GetPostWithComments.
- Create a new Person entity called author. This entity is a “stub”, since it is only going to hold the key.
- Add a new Comment to the Post, by filling the author, title, and body.
- Send the proposed change to the service through UpdatePostWithComments.
Another interesting method to observe is DeleteAllComments:
private static void DeleteAllComments(int postID)
{
using (var service = new BloggingClient())
{
var post = service.GetPostWithComments(postID);
foreach (var comments in post.Comments.ToArray())
{
comments.MarkAsDeleted();
}
service.UpdatePostWithComments(post);
}
}
The method follows this sequence:
- Creates an instance of the service client within a using statement.
- Retrieves a blog post from the service using GetPostWithComments.
- Marks each comment for the post as Deleted.
Notice that the foreach loop is performed over a copy of the original collection of comments. This has to be the case because MarkAsDeleted will remove the entity from the collection, and modifying the enumeration over which foreach is looping would cause an exception.
If you want to test how the service validation works, you can try passing an empty string for title or the body of a comment, by adding a line like this:
AddComment(postID, authorID, "Bad post", "");
Final stop
You can learn a lot more about self tracking entities while executing and stepping through this sample program. Before starting it, make sure the BloggingConsole project is the start up project by right clicking on the project entry on the Solution Explorer and then choosing the “Set as StartUp Project” menu item.
Here is the default output of the program:
The walkthrough has described some basic usage of self tracking entities. We hope you have learned enough about how to use self tracking entities that you can experiment with the Feature CTP1 version on your own code.
There are many areas in which we expect to be improving self-tracking entities in the next release of the Entity Framework Feature CTP. Please let us know of any issues you find.
Notes
Why not POCO Proxies
Alongside POCO support, we added the capability to create dynamic proxies for entities that comply with certain requirements. Self tracking entities do not make use of this feature for several reasons:
1. Self tracking entities are optimized for serialization scenarios, and dynamic proxy types complicate WCF serialization. Before serializing, we would need to take advantage of WCF interception mechanisms to “convert” the instance to serialize back to the original POCO types.
2. One of the characteristics of POCO Proxies is their ability to perform lazy loading. Using serialization and lazy loading together requires much care, since lazy loading may cause unintended data to be queried and included in the graph.
3. In the main scenario we are trying to address in this release, most changes happen on the client side. While change tracking proxies optimize change tracking performance, that only happens when the entities are being tracked by the ObjectStateManager. The default snapshot-based change tracking mechanism used for pure POCO objects provides enough functionality for self tracking entities.
Database generation scripts
If you need to create the database outside the project, you will find database creation scripts in the App_Data folder of the BloggingService project.
- Diego Vega
Program Manager, Entity Framework