The official source of information on Managed Providers, DataSet & Entity Framework from Microsoft
The goal of this walkthrough is to demonstrate a basic scenario for the use of the Self-Tracking Entities feature of Entity Framework.
Self-Tracking Entities consist on a set of code generation templates based on Text Template Transformation Toolkit (T4) technology that is used to generate entity types that have the ability to record changes on scalar and complex property values, and on reference and collection navigation properties, independently of the Entity Framework.
The main scenario we are trying to address with Self-Tracking Entities is one in which a WCF service exposes a series of operations that return entity graphs, then a client application can manipulate that graph and submit the modifications to another service operation that validates changes in the graph and then applies them to the database store using Entity Framework.
While the following walkthrough uses C#, versions of the Self-Tracking Entities template exist for both C# and Visual Basic, and all the concepts described apply as well to the Visual Basic version.
Requirements
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:
To start, open the EDM model named Blogging.edmx that is included in the BlogginModel project.
The first step will be adding a T4 template for generating the self-tracking entities. In order to do that, from any blank area in the surface of the EDM designer, right click and select the “Add Code Generation Item…”
This will display a standard “Add New Item” dialog. Select the item type “ADO.NET Self-Tracking Entity 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 may 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.
While self-tracking entities code is generated using the same mechanisms as the default code generation, it takes advantage of the new support for Persistence Ignorance in Entity Framework, and therefore the code generated does not contain any attribute or type defined by Entity Framework. Thanks to this, entities generated by Self-Tracking Entities Template can be used in Silverlight 3. We will describe how to use self-tracking entities in Silverlight in an upcoming walkthrough.
You can browse the Person.cs file under Blogging.Types.tt to get an idea of what it involves.
A few things to notice in this code:
To make it possible to obtain instances of self-tracking entities on the client, we are going to share the code for the types themselves between the service and the client.
Note: Self-tracking entities are not well suited for applications in which sharing the entity types between the client and the server is not possible or practical, like cases in which you only control either the client or the service, but not both. When developing applications for distributed environments in which sharing the types is not possible, consider a solution based on ADO.NET Data Services or custom Data Transfer Objects. Entity Framework’s API can be used to build custom N-Tier solutions. For more information, see here.
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 now 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 “TextTemplatingFileGenerator” and leave it blank. Also, notice that some generated code 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 any remaining generated files underneath.
We are now going to need a new Windows Class Library project 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. We are not going to use it in this walkthrough.
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 by default the TT file needs to be located alongside an EDMX file in order to obtain all its metadata. However, in the case of T4 templates, links to generated code files are also added to the current project and their contents compiled to the output assembly as well. Since the link is specified using relative paths, the ability to locate the EDMX file is preserved if you move the solution to another directory or in source control.
Note: there are two main ways to move or share the code generated by an Entity Framework T4 template to a different project:
In this walkthrough we are going to use the first method.
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:
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:
Since the BloggingModel project contains the EDMX file, the output assembly will by default 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, so you won’t need to add it:
<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>
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 class library project.
The persistence layer is going to be composed of the derived 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: 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 its “Custom Tool Namespace” property to “BloggingEntities”.
The service interface is contained in the IBlogging.cs file. The interface defines four methods:
To continue with the walkthrough, un-comment the code contained in IBlogging.cs.
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 that is new about these methods with respect to anything you could previously do with Entity Framework is that they return graphs of self-tracking entities instead of regular entities.
We are now going to focus on the most interesting operation, UpdatePostWithComments, which leverages self-tracking entities in order to save changes that have happened to the object graph on the client.
In order to proceed, uncomment all the commented code in Blogging.svc file.
This method takes a self-tracking Post 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 to the database.
public void UpdatePostWithComments(Post post){ ValidateUpdate(post); ApplyDefaults(post); using (var context = new BloggingContainer()) { context.Entries.ApplyChanges(post); context.SaveChanges(); }}
You should always validate the input of the update method. 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.
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 will throw an exception if any of the following conditions occur:
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; } }}
At the core of UpdatePostWithComments, we are using the ApplyChanges extension method that has been generated into the Blogging.Context.Extensions.cs file.
The goal of ApplyChanges is to examine the change tracking information contained in the graph of self-tracking entities to infer the set of operations that need to be performed to reflect the changes in the database.
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:
It is worth mentioning that the client doesn’t need EDM metadata at runtime: most of the behaviors that Entity Framework would regularly derive from the metadata are already hardcoded in the 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.
Important: 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.
Before we proceed to look at the usage of self-tracking entities on the client tier, it is opportune to review some of the ways you can control self-tracking entities. The following are extension methods can be applied to any self-tracking entity:
post.StopTracking();comment.StopTracking();post.Orders.Add(comment);post.StartTracking();comment.StartTracking();
post.Author = new Person { ID = authorID }.MarkAsUnchanged();
In order to proceed with the walkthrough now, uncomment the methods on the file Progam.cs, which contains some simple scenario tests for the service. The program follows this sequence:
Notice that each separate method creates and disposes its own instance of the service client, just for the purpose of testing operations independently.
You can learn a lot more about self-tracking entities while stepping through the Console program and by making adjustments to the code. Make sure you right click on the BloggingConsole project entry on the Solution Explorer and then choosing the “Set as StartUp Project” menu item.
Here is the default output of the program when you execute it:
One of the most interesting methods 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 }.MarkAsUnchanged(); post.Comments.Add(new Comment { Author = author, Title = title, Body = body }); service.UpdatePostWithComments(post); }}
The method follows this sequence:
Note: Newly created self-tracking entities are marked as Added by default. The stub entity of type Person used in this method is moved to the Unchanged state explicitly with the MarkAsUnchanged extension method. Unless we do this, when ApplyChanges back on the service will specify that this is a new Person that needs to be added to the database. Then, SaveChanges will fail while trying to insert a row with a duplicate key.
The use of a stub entity described above is a common pattern used in Object/Relational Mappers. Since Entity Framework now supports foreign keys, there is an alternative way to relate the Comment with its Author: provided we know the primary key value of the Person that we want to assign as an author, we can set the value to the AuthorID property in the Comment. Using foreign keys the method above would change to this:
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 }.MarkAsUnchanged(); post.Comments.Add(new Comment { AuthorID = authorID, Title = title, Body = body }); service.UpdatePostWithComments(post); }}
With foreign keys, stub entities become unnecessary. In any case, self-tracking entities can use both foreign key associations and independent associations as they appear in the model.
Another interesting method to look at 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); }}
Note: 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 server-side validation behaves, 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", "");
The walkthrough has described basic usage of the Self-Tracking Entities feature. We hope you have learned enough about how to use self-tracking entities that you can experiment with the Feature CTP 2 version on your own code.
There are some areas in which we expect to be improving self-tracking entities in the final release of Visual Studio 2010. Please let us know of any issues you find.
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.
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.