CHAPTER 9

image

Using the Entity Framework in N-Tier Applications

Not all applications can be neatly bundled into a single process (that is, reside on a single physical server). In fact, in this ever-increasingly networked world, many application architectures support the classic logical layers of presentation, application, and data and also are physically deployed across multiple computers. While logically layering an application on a single computer can be accommodated in a single Application Domain without much concern for proxies, marshalling, serialization, and network protocols, applications that span from something as small as a mobile device to an enterprise application server found in a data center need to take all of these considerations into account. Fortunately, the Entity Framework together with technologies like Microsoft’s Windows Communication Foundation, or the Microsoft Web API framework, are well suited for these types of n-Tier applications.

In this chapter, we’ll cover a wide range of recipes for using the Entity Framework with n-Tier applications. To be clear, n-Tier is defined as an application architecture in which the presentation, business logic, and data access processing tiers are physically separated across multiple servers. This physical separation can help improve the scalability, maintainability, and future extensibility of an application, but often can have a negative impact on performance, as we are now crossing physical machine boundaries when processing application operations.

N-Tier architecture adds some special challenges to the change-tracking features of Entity Framework. Initially, data is fetched with an Entity Framework context object that is destroyed after the data is sent to the client. While on the client, changes made to the data are not tracked. Upon an update, a new context object must be created to process the submitted data. Obviously, the new context object knows nothing of the previous context object, nor the values of the original entities. In this chapter, we’ll look at some approaches that you can implement to help bridge this gap.

In past versions of Entity Framework, a developer could leverage a special template entitled Self-Tracking Entities, which provided built-in plumbing to help track changes to disconnected entity objects. However, in Entity Framework 6, the self-tracking entity approach has been deprecated. While the legacy ObjectContext will support self-tracking entities, the more recent DbContext object does not. The recipes in this chapter focus on basic create, read, update, and delete operations you’ll typically use in your n-Tier applications. Additionally, we’ll take a deep dive into entity and proxy serialization, concurrency, and working with the unique challenges of tracking entity changes outside the scope of an object context.

9-1. Updating Single Disconnected Entities with the Web API

Problem

You want to leverage REST-based Web API services for inserts, deletes, and updates to a data store. Additionally, you want to implement the code-first approach for Entity Framework 6 to manage data access.

In this example, we emulate an n-Tier scenario where a stand-alone client application (Console Application) is calling a stand-alone website (Web API Project) that exposes REST-based services. Note that each tier is contained in a separate Visual Studio Solution, so as to allow for easier configuring, debugging, and simulation of an n-Tier application.

Solution

Let’s say that you have a model like the one shown in Figure 9-1.

9781430257882_Fig09-01.jpg

Figure 9-1. A model for orders

Our model represents Orders. We want to put the model and database code behind a Web API service so that any client that consumes HTTP can insert, update, and delete data into orders. To create the service, perform the following steps:

  1. Create a new ASP.NET MVC 4 Web Application project, selecting the Web API template from the Project Templates wizard. Name the project Recipe1.Service.
  2. Add a new Web API Controller to the project entitled OrderController.
  3. As shown in Listing 9-1, add the Order entity class.

    Listing 9-1.  Order Entity Class

    public class Order
    {
        public int OrderId { get; set; }
        public string Product { get; set; }
        public int Quantity { get; set; }
        public string Status { get; set; }
        public byte[] TimeStamp { get; set; }
    }
  4. Add a reference in the Recipe1.Service project to the Entity Framework 6 libraries. Leveraging the NuGet Package Manager does this best. Right-click on Reference, and select Manage NuGet Packages. From the Online tab, locate and install the Entity Framework 6 package. Doing so will download, install and configure the Entity Framework 6 libraries in your project.
  5. Then add a new class entitled Recipe1Context, and add the code from Listing 9-2 to it, ensuring that the class derives from the Entity Framework DbContext class.

    Listing 9-2.  Context Class

    public class Recipe1Context : DbContext
    {
        public Recipe1Context() : base("Recipe1ConnectionString") { }
     
        public DbSet<Order> Orders { get; set; }
      
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Order>().ToTable("Chapter9.Order");
            // Following configuration enables timestamp to be concurrency token
            modelBuilder.Entity<Order>().Property(x => x.TimeStamp)
                .IsConcurrencyToken()
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
        }
    }
  6. Next, from Listing 9-3, add the RecipeConnectionString connection string to the Web.Config file under the ConnectionStrings section.

    Listing 9-3.  Connection String for the Recipe1 Web API Service

    <connectionStrings>
      <add name="Recipe1ConnectionString"
        connectionString="Data Source=.;
           Initial Catalog=EFRecipes;
           Integrated Security=True;
           MultipleActiveResultSets=True"
        providerName="System.Data.SqlClient" />
    </connectionStrings>
  7. Then add the code in Listing 9-4 to the Application_Start method in the Global.asax file. This code will disable the Entity Framework Model Compatibility check.

    Listing 9-4.  Disable the Entity Framework Model Compatibility Check

    protected void Application_Start()
    {
        // Disable Entity Framework Model Compatibilty
        Database.SetInitializer<Recipe1Context>(null);
        ...
    }
  8. Finally, replace the code in the OrderController with that from Listing 9-5.

    Listing 9-5.  Code for the OrderController

    public class OrderController : ApiController
    {
        // GET api/order
        public IEnumerable<Order> Get()
        {
            using (var context = new Recipe1Context())
            {
                return context.Orders.ToList();
            }
        }
      
        // GET api/order/5
        public Order Get(int id)
        {
            using (var context = new Recipe1Context())
            {
                return context.Orders.FirstOrDefault(x => x.OrderId == id);
            }
        }
      
        // POST api/order
        public HttpResponseMessage Post(Order order)
        {
            // Cleanup data from previous requests
            Cleanup();
     
            using (var context = new Recipe1Context())
            {
                context.Orders.Add(order);
                context.SaveChanges();
      
                // create HttpResponseMessage to wrap result, assigning Http Status code of 201,
                // which informs client that resource created successfully
                var response = Request.CreateResponse(HttpStatusCode.Created, order);
     
                // add location of newly-created resource to response header
                                response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = order.OrderId }));
                    
                return response;
            }
        }
      
        // PUT api/order/5
        public HttpResponseMessage Put(Order order)
        {
            using (var context = new Recipe1Context())
            {
                context.Entry(order).State = EntityState.Modified;
                context.SaveChanges();
                      
        // return Http Status code of 200, informing client that resouce updated successfully
                return Request.CreateResponse(HttpStatusCode.OK, order);
            }
        }
      
        // DELETE api/order/5
        public HttpResponseMessage Delete(int id)
        {
            using (var context = new Recipe1Context())
            {
                    var order = context.Orders.FirstOrDefault(x => x.OrderId == id);
                    context.Orders.Remove(order);
                    context.SaveChanges();

              // Return Http Status code of 200, informing client that resouce removed successfully
              return Request.CreateResponse(HttpStatusCode.OK);
            }
        }
      
        private void Cleanup()
        {
            using (var context = new Recipe1Context())
            {
                context.Database.ExecuteSqlCommand("delete from chapter9.[order]");
            }
        }
    }

    It’s important to point out that when using Entity Framework with MVC or Web API, these ASP.NET frameworks contain a great deal of scaffolding (i.e., code generation tempates) that can generate a functioning controller that contains Entity Framework plumbing code for you, saving you the effort of constructing it manually.

    Next we create the client solution, which will consume the Web API service.

  9. Create a new Visual Studio solution that contains a Console application entitled Recipe1.Client.
  10. Add the same Order entity class to the client that we added to the service back in Listing 9-1.

    Finally, replace the code in the program.cs file with that from Listing 9-6.

    Listing 9-6.  Our Windows Console Application That Serves as Our Test Client

    private HttpClient _client;
    private Order _order;
     
    private static void Main()
    {
        Task t = Run();
        t.Wait();
        Console.WriteLine(" Press <enter> to continue...");
        Console.ReadLine();
    }
      
    private static async Task Run()
    {
        // create instance of the program class
        var program = new Program();
        program.ServiceSetup();
        program.CreateOrder();
        // do not proceed until order is added
        await program.PostOrderAsync();
        program.ChangeOrder();
        // do not proceed until order is changed
         await program.PutOrderAsync();
        // do not proceed until order is removed
       await  program.RemoveOrderAsync();
    }
      
    private void ServiceSetup()
    {
        // map URL for Web API cal
        _client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") };
      
        // add Accept Header to request Web API content
        // negotiation to return resource in JSON format
        _client.DefaultRequestHeaders.Accept.
            Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }
      
    private void CreateOrder()
    {
        // Create new order
        _order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" };
    }
      
    private async Task PostOrderAsync()
    {
        // leverage Web API client side API to call service
        var response = await _client.PostAsJsonAsync("api/order", _order);
        Uri newOrderUri;
      
        if (response.IsSuccessStatusCode)
        {
            // Capture Uri of new resource
            newOrderUri = response.Headers.Location;
      
            // capture newly-created order returned from service,
            // which will now include the database-generated Id value
            _order = await response.Content.ReadAsAsync<Order>();
            Console.WriteLine("Successfully created order. Here is URL to new resource: {0}", newOrderUri);
        }
        else
            Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
    }
      
    private void ChangeOrder()
    {
        // update order
        _order.Quantity = 10;
    }
      
    private async Task PutOrderAsync()
    {
        // construct call to generate HttpPut verb and dispatch
        // to corresponding Put method in the Web API Service
        var response = await _client.PutAsJsonAsync("api/order", _order);
      
        if (response.IsSuccessStatusCode)
        {
            // capture updated order returned from service, which will include new quanity
            _order = await response.Content.ReadAsAsync<Order>();
            Console.WriteLine("Successfully updated order: {0}", response.StatusCode);
        }
        else
            Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
    }
      
    private async Task RemoveOrderAsync()
    {
        // remove order
        var uri = "api/order/" + _order.OrderId;
        var response = await _client.DeleteAsync(uri);
      
        if (response.IsSuccessStatusCode)
            Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode);
        else
            Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
     }

The following is the output of our test client from Listing 9-6:

Successfully created order: http://localhost:3237/api/order/1054
Successfully updated order: OK
Sucessfully deleted order: OK

How It Works

Start by running the Web API application. The Web API application contains an MVC Web Controller that, when started, will bring up a home page. At this point, the site is running and its services are available.

Next open the console application, set a breakpoint on the first line of code in the program.cs file, and run the console application. First we establish some basic plumbing—mapping the Web API service URI and configuring the Accept Header—that will ask the Web API service to return the data in a JSON format. Then we create an Order object, which we send to the Web API service by calling the PostAsJsonAsync method from the HttpClient object. If you place a breakpoint in the Post Action Method in the Order Web API controller class, you’ll see that it receives the order object as a parameter and adds it to the Order entity in the context object. Doing so marks the object as added and causes the context to start tracking it. Finally, we call the SaveChanges method to insert the new data into the underlying data store. We then wrap a HTTP status code of 201 and the URI location of the newly created resource into an HttpResponseMessage object and return it to the calling application. When using the ASP.NET Web API, we want to ensure that our client generates an HTTP Post verb when inserting new data. The HTTP Post verb will invoke the corresponding Post action method in the Web API controller.

Back in the client, we execute our next operation, changing the quantity of order and sending the entity back to the Web API service by calling the PutAsJsonAsync method from the HttpClient object. If you place a breakpoint in the Put Action Method in the Order Web API controller class, you’ll see that it receives the order object as a parameter in the service. From the context object, we invoke the Entry method, passing in the Order entity reference. Then, by setting the State property to Modified attaches the entity of the underlying context object. The subsequent call to SaveChanges generates a SQL Update statement. In this case, we update all columns for order. In later recipes, we’ll see how we can update only those properties that have changed. We complete the operation by sending an HttpResponseMethod to caller with a HTTP status code of 200.

Back in the client, we invoke our final operation, which will delete the Order entity from the underlying data store. We append the Id for the order as an additional URI segment and call the Web API service with the DeleteAsync method from the HttpClient object. In the service, we retrieve the target order from the data store and pass its reference to the Remove method, called from the Order entity and context object. Doing so marks the entity as deleted. The subsequent call to SaveChanges generates a SQL Delete statement that removes the order from the underlying data store.

In this recipe, we’ve seen that we can encapsulate Entity Framework data operations behind a Web API service. The client can consume the service by using the HttpClient object that is exposed by the Web API client API. Adhering to the Web API’s HTTP verb-based dispatch, we leverage the Post action method to add a new record, the Put action method to update a record, and the Delete action method to remove a record. Also in the recipe, we implement Entity Framework using the code-first approach.

In a production application, we would most likely create a separate layer (Visual Studio class project) to separate the Entity Framework data access code from the Web API service.

9-2.  Updating Disconnected Entities with WCF

Problem

You want to use a Windows Communication Foundation (WCF) service to expose selects, inserts, deletes, and updates for a data store and keep the database operations as simple as possible. Additionally, you want to implement the code-first approach for Entity Framework 6 to manage data access.

Solution

Let’s say that you have a model like the one shown in Figure 9-2.

9781430257882_Fig09-02.jpg

Figure 9-2. A model for blog posts and comments

Our model represents blog posts and the comments that readers have about the posts. To make things clearer, we’ve stripped out most of the properties that we would normally have, such as the body of the post, the author, the date and time of the post, and so on.

We want to put all of the database code behind a WCF service so that clients can read, update, and delete posts and comments, as well as insert new ones. To create the service, do the following:

  1. Create a new Visual Studio solution, adding a c# class library project. Name the class library Recipe2.
  2. Add a reference to the Entity Framework 6 libraries in the new project. Leveraging the NuGet Package Manager does this best. Right-click on Reference, and select Manage NuGet Packages. From the Online tab, locate and install the Entity Framework 6 package.
  3. Add three classes to the Recipe2 project: Post, Comment, and Recipe2Context. Post and Comment represent POCO entity classes that will directly map to the corresponding Post and Comment tables. Recipe2Context is the DbContext object that will serve as the gateway to Entity Framework functionality. Make sure that you include the required WCF DataContract and DataMember attributes in the entity classes as shown in Listing 9-7.

    Listing 9-7.  Our POCO Classes Post, Comment, and Our Recipe2Context Context Object

    [DataContract(IsReference = true)]
    public class Post
    {
        public Post()
        {
            comments = new HashSet<Comments>();
        }
     
        [DataMember]
        public int PostId { get; set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public virtual ICollection<Comment> Comments { get; set; }
    }
     
    [DataContract(IsReference=true)]
    public class Comment
    {
        [DataMember]
        public int CommentId { get; set; }
        [DataMember]
        public int PostId { get; set; }
        [DataMember]
        public string CommentText { get; set; }
        [DataMember]
        public virtual Post Post { get; set; }
    }
     
    public class EFRecipesEntities : DbContext
    {
        public EFRecipesEntities()
            : base("name=EFRecipesEntities")
        {
        }
     
        public DbSet<Post> posts;
        public DbSet<Comment> comments;
     
    }
  4. Add an App.config file to the Recipe2 project, and copy the connection string from Listing 9-8.

    Listing 9-8.  The Connection String for the Recipe1 Class Library

    <connectionStrings>
      <add name="Recipe2ConnectionString"
      connectionString="Data Source=.;
          Initial Catalog=EFRecipes;
          Integrated Security=True;
          MultipleActiveResultSets=True"
      providerName="System.Data.SqlClient" />
    </connectionStrings>
  5. Next add a WCF service project to the solution. Use the default name Service1 just to keep things simple. Change the IService1.cs file to reflect the new IService1 interface in Listing 9-9.

    Listing 9-9.  The Service Contract for Our Service

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        void Cleanup();
     
        [OperationContract]
        Post GetPostByTitle(string title);
     
        [OperationContract]
        Post SubmitPost(Post post);
     
        [OperationContract]
        Comment SubmitComment(Comment comment);
     
        [OperationContract]
        void DeleteComment(Comment comment);
    }
  6. Change the service application code in the Service1.svc.cs file using the code from Listing 9-10. Add a project reference to the Recipe2 class library and a using statement so that the references to the POCO classes resolve correctly. You will also need to add a reference to Entity Framework 6 libraries.

    Listing 9-10.  The Implementation of the Service Contract in Listing 9-9. (Be sure to add references to System.Data.Entity and System.Security to this project.)

    public class Service1 : IService
    {
        public void Cleanup()
        {
            using (var context = new EFRecipesEntities())
            {
                context.Database.ExecuteSqlCommand("delete from chapter9.comment");
                context. Database.ExecuteSqlCommand ("delete from chapter9.post");
            }
        }
     
        public Post GetPostByTitle(string title)
        {
            using (var context = new EFRecipesEntities())
            {
                context.Configuration.ProxyCreationEnabled = false;
                var post = context.Posts.Include(p => p.Comments)
                                  .Single(p => p.Title == title);
                return post;
            }
        }
     
        public Post SubmitPost(Post post)
        {
                context.Entry(post).State =
                    // if Id equal to 0, must be insert; otherwise, it's an update
                post.PostId == 0 ? EntityState.Added : EntityState.Modified;
                context.SaveChanges();
                return post;
        }
     
        public Comment SubmitComment(Comment comment)
        {
            using (var context = new EFRecipesEntities())
            {
                context.Comments.Attach(comment);
                if (comment.CommentId == 0)
                {
                    // this is an insert
                    context.Entry(comment).State = EntityState.Added);
                }
                else
                {
                    // set single property to modified, which sets state of entity to modified, but
                    // only updates the single property – not the entire entity
                    context.entry(comment).Property(x => x.CommentText).IsModified = true;
                }
                context.SaveChanges();
                return comment;
            }
        }
     
        public void DeleteComment(Comment comment)
        {
            using (var context = new EFRecipesEntities())
            {
                context.Entry(comment).State = EntityState.Deleted;
                context.SaveChanges();
            }
        }
    }
  7. Finally, add a Windows Console Application to the service project. We’ll use this for our client to test the WCF service. Copy the code from Listing 9-11 into the Program class in the Console application. Right-click the console application project, select Add Service Reference, and add a reference to the Service1 service. You will also need to add a project reference to the class library created in step 1 or expose it through a proxy class from the service.

    Listing 9-11.  Our Windows Console Application That Serves as Our Test Client

    class Program
    {
        static void Main(string[] args)
        {
            using (var client = new ServiceReference2.Service1Client())
            {
                // cleanup previous data
                client.Cleanup();
     
                // insert a post
                var post = new Post { Title = "POCO Proxies" };
                post = client.SubmitPost(post);
     
                // update the post
                post.Title = "Change Tracking Proxies";
                client.SubmitPost(post);
     
                // add a comment
                var comment1 = new Comment {
                     CommentText = "Virtual Properties are cool!",
                     PostId = post.PostId };
                var comment2 = new Comment {
                     CommentText = "I use ICollection<T> all the time",
                     PostId = post.PostId };
                comment1 = client.SubmitComment(comment1);
                comment2 = client.SubmitComment(comment2);
     
                // update a comment
                comment1.CommentText = "How do I use ICollection<T>?";
                client.SubmitComment(comment1);
     
                // delete comment 1
                client.DeleteComment(comment1);
     
                // get posts with comments
                var p = client.GetPostByTitle("Change Tracking Proxies");
                Console.WriteLine("Comments for post: {0}", p.Title);
                foreach (var comment in p.Comments)
                {
                    Console.WriteLine(" Comment: {0}", comment.CommentText);
                }
            }
        }
    }

The following is the output of our test client from Listing 9-11:

Comments for post: Change Tracking Proxies
        Comment: I use ICollection<T> all the time

How It Works

Let’s start with the Windows console application, which is our test client for the service. We create an instance of our service client in a using {} block. Just as we’ve done when creating an instance of an Entity Framework context in a using {} block, this ensures that Dispose() is implicitly called when we leave the block, either normally or via an exception.

Once we have an instance of our service client, the first thing we do is call the Cleanup() method. We do this to remove any previous test data we might have.

With the next couple of lines, we call the service’s SubmitPost() method. In this method’s implementation (see Listing 9-10), we interrogate the value of PostId. If the PostId is 0, we then assume it’s a new post and set the Entity State to Added. Otherwise, we assume the entity exists and we are modifying it, thus setting the Entity State to Modified. Although somewhat crude, this approach can determine the state (new or existing) of the post entity, depending on the domain of the valid Ids for a post as well as the runtime initializing integers to 0. A better approach might involve sending an additional parameter to the method or creating a separate InsertPost() method. The best approach depends on the structure of your application.

If the post is to be inserted, we change the object state of the post to EntityState.Added. Otherwise, we change its object state to EntityState.Modified. The EntityState value drives whether an insert or update statement is generated. If the post is inserted, the post instance’s PostId is updated with the new correct value. The post is returned.

Inserting and updating a single property on the Comment entity is similar to inserting and updating a post with one significant difference: as a business rule, when we update a comment, we want to make sure only to update the CommentText property. This property holds the body of the comment, and we don’t want to update any other part of the Comment entity object. To do this, we mark just the CommentText property as modified. Entity Framework will generate a simple update statement that changes just the CommentText column in the database. Note that this works as we are just changing a single property of the entity. If we were changing multiple properties on the Comment entity, we would then need some way to track which properties were changed on the client. In cases where multiple properties can change, it is often more efficient to update the entity object, without need for complex client-side change tracking.

To delete a comment, we call the Entity() method on the context object, passing in the comment entity as an argument and set the EntityState to Deleted, which marks the comment for deletion and generates a SQL Delete statement.

Finally, the GetPostByTitle() method eagerly loads the comments for each post and returns an object graph of posts and related comments. Because we have implemented POCO classes, Entity Framework returns what is called a dynamic proxy object that wraps the underlying post and comments class. Unfortunately, WCF cannot serialize a proxy object. However, with the line ProxyCreationEnabled = false, we simply disable proxy class generation for the query and Entity Framework returns the actual objects. If we attempted to serialize the proxy object, we would receive the following error message:

The underlying connection was closed: The connection was closed unexpectedly

We could even move the ProxyCreationEnabled = false to the constructor of the service to enforce it for all the service methods.

In this recipe, we’ve seen that we can use POCO objects to handle CRUD operations with WCF. Because there is no state information stored on the client, we’ve built separate methods for inserting, updating, and deleting posts and comments. Other recipes in this chapter will demonstrate techniques used to reduce the number of methods our service must implement and to simplify the communication between the client and the server.

9-3.  Finding Out What Has Changed with Web API

Problem

You want to leverage REST-based Web API services for database insert, delete, and update operations for an object graph without having to expose a separate method for updating each entity class. Additionally, you want to implement the code-first approach for Entity Framework 6 to manage data access.

In this example, we emulate an n-Tier scenario where a stand-alone client application (Console Application) is calling a stand-alone website (Web API Project) that exposes REST-based services. Note that each tier is contained in a separate Visual Studio Solution, to allow for easier configuring, debugging and simulation of an n-Tier application. Let’s say that you have a model like the one shown in Figure 9-3.

9781430257882_Fig09-03.jpg

Figure 9-3. A model for Travel Agents and Bookings

Our model represents Travel Agents and their corresponding Bookings. We want to put the model and database code behind a Web API service so that any client that consumes HTTP can insert, update, and delete orders. To create the service, perform the following steps:

  1. Create a new ASP.NET MVC 4 Web Application project, selecting the Web API template from the Project Templates wizard. Name the project Recipe3.Service.
  2. Add a new Web API Controller to the project entitled TravelAgentController.
  3. Next, from Listing 9-12 add the TravelAgent and Booking entity classes.

    Listing 9-12.  Travel Agent and Booking Entity Classes

    public class TravelAgent
    {
        public TravelAgent()
        {
            this.Bookings = new HashSet<Booking>();
        }
      
        public int AgentId { get; set; }
        public string Name { get; set; }
      
        public virtual ICollection<Booking> Bookings { get; set; }
    }
     
     
    public class Booking
    {
        public int BookingId { get; set; }
        public int AgentId { get; set; }
        public string Customer { get; set; }
        public DateTime BookingDate { get; set; }
        public bool Paid { get; set; }
      
        public virtual TravelAgent TravelAgent { get; set; }
    }
  4. Add a reference in the Recipe3.Service project to the Entity Framework 6 libraries. Leveraging the NuGet Package Manager does this best.  Right-click on Reference, and select Manage NuGet Packages. From the Online tab, locate and install the Entity Framework 6 package.
  5. Then add a new class entitled Recipe3Context, and add the code from Listing 9-13 to it, ensuring that the class derives from the Entity Framework DbContext class.

    Listing 9-13.  Context Class

    public class Recipe3Context : DbContext
    {
        public Recipe3Context() : base("Recipe3ConnectionString") { }
     
        public DbSet<TravelAgent> TravelAgents { get; set; }
        public DbSet<Booking> Bookings { get; set; }
      
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<TravelAgent>().HasKey(x => x.AgentId);
            modelBuilder.Entity<TravelAgent>().ToTable("Chapter9.TravelAgent");
            modelBuilder.Entity<Booking>().ToTable("Chapter9.Booking");
        }
    }
  6. Next, from Listing 9-14, add the Recipe3ConnectionString connection string to the Web.Config file under the ConnectionStrings section.

    Listing 9-14.  Connection String for the Recipe1 Web API Service

    <connectionStrings>
      <add name="Recipe3ConnectionString"
        connectionString="Data Source=.;
           Initial Catalog=EFRecipes;
           Integrated Security=True;
           MultipleActiveResultSets=True"
        providerName="System.Data.SqlClient" />
    </connectionStrings>
  7. Then add the code in Listing 9-15 to the Application_Start method in the Global.asax file. This code will disable the Entity Framework Model Compatibility check and instruct the JSON serializer to ignore the self-referencing loop caused by navigation properties being bidirectional between TravelAgent and Booking.

    Listing 9-15.  Disable the Entity Framework Model Compatibility Check

    protected void Application_Start()
    {
        // Disable Entity Framework Model Compatibilty
        Database.SetInitializer<Recipe1Context>(null);
     
        // The bidirectional navigation properties between related entities
        // create a self-referencing loop that breaks Web API's effort to
        // serialize the objects as JSON. By default, Json.NET is configured
        // to error when a reference loop is detected. To resolve problem,
        // simply configure JSON serializer to ignore self-referencing loops.
        GlobalConfiguration.Configuration.Formatters.JsonFormatter
            .SerializerSettings.ReferenceLoopHandling =
                Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        ...
    }
  8. Modify the Web API routing by changing the code in RouteConfig.cs file to match that of Listing 9-16.

    Listing 9-16.  Modifications to RouteConfig Class to Accommodate RPC-Style Routing

    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "ActionMethodSave",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
  9. Finally, replace the code in the TravelAgentController with that from Listing 9-17.

    Listing 9-17.  Travel Agent Web API Controller

    public class TravelAgentController : ApiController
    {
        // GET api/travelagent
        [HttpGet]
        public IEnumerable<TravelAgent> Retrieve()
        {
            using (var context = new Recipe3Context())
            {
                return context.TravelAgents.Include(x => x.Bookings).ToList();
            }
        }
      
        /// <summary>
        /// Update changes to TravelAgent, implementing Action-Based Routing in Web API
        /// </summary>
        public HttpResponseMessage Update(TravelAgent travelAgent)
        {
            using (var context = new Recipe3Context())
            {
                var newParentEntity = true;
      
                // adding the object graph makes the context aware of entire
                // object graph (parent and child entities) and assigns a state
                // of added to each entity.
                context.TravelAgents.Add(travelAgent);
     
                if (travelAgent.AgentId > 0)
                {
                    // as the Id property has a value greater than 0, we assume
                    // that travel agent already exists and set entity state to
                    // be updated.
                    context.Entry(travelAgent).State = EntityState.Modified;
                    newParentEntity = false;
                }
     
                // iterate through child entities, assigning correct state.
                foreach (var booking in travelAgent.Bookings)
                {
                    if (booking.BookingId > 0)
                        // assume booking already exists if ID is greater than zero.
                        // set entity to be updated.
                        context.Entry(booking).State = EntityState.Modified;
                }
      
                context.SaveChanges();
      
                HttpResponseMessage response;
      
                // set Http Status code based on operation type
                response = Request.CreateResponse(newParentEntity
                     ? HttpStatusCode.Created : HttpStatusCode.OK, travelAgent);
      
                return response;
            }
    }
      
    [HttpDelete]
    public HttpResponseMessage Cleanup()
    {
        using (var context = new Recipe3Context())
        {
            context.Database.ExecuteSqlCommand("delete from chapter9.booking");
            context.Database.ExecuteSqlCommand("delete from chapter9.travelagent");
        }
      
            return Request.CreateResponse(HttpStatusCode.OK);
        }
      
    }

    Next we create the client Visual Studio solution that will consume the Web API service.

  10. Create a new Visual Studio solution that contains a Console application entitled Recipe3.Client.
  11. Replace the code in the program.cs file with that from Listing 9-18.

    Listing 9-18.  Our Windows Console Application That Serves as Our Test Client

    internal class Program
    {
        private HttpClient _client;
        private TravelAgent _agent1, _agent2;
        private Booking _booking1, _booking2, _booking3;
        private HttpResponseMessage _response;
      
        private static void Main()
        {
            Task t = Run();
            t.Wait();
            Console.WriteLine(" Press <enter> to continue...");
            Console.ReadLine();
        }
      
        private static async Task Run()
        {
            var program = new Program();
            program.ServiceSetup();
            // do not proceed until clean-up is completed
            await program.CleanupAsync();
            program.CreateFirstAgent();
            // do not proceed until agent is created
            await program.AddAgentAsync();
            program.CreateSecondAgent();
            // do not proceed until agent is created
            await program.AddSecondAgentAsync();
            program.ModifyAgent();
            // do not proceed until agent is updated
            await program.UpdateAgentAsync();
            // do not proceed until agents are fetched
            await program.FetchAgentsAsync();
        }
      
        private void ServiceSetup()
        {
            // set up infrastructure for Web API call
            _client = new HttpClient {BaseAddress = new Uri("http://localhost:6687/")};
      
            // add Accept Header to request Web API content negotiation to return resource in JSON format
            _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
      
        private async Task CleanupAsync()
        {
            // call cleanup method in service
            _response = await _client.DeleteAsync("api/travelagent/cleanup/");
        }
      
        private void CreateFirstAgent()
        {
            // create new Travel Agent and booking
            _agent1 = new TravelAgent {Name = "John Tate"};
            _booking1 = new Booking
            {
                Customer = "Karen Stevens",
                Paid = false,
                BookingDate = DateTime.Parse("2/2/2010")
            };
            _booking2 = new Booking
            {
                Customer = "Dolly Parton",
                Paid = true,
                BookingDate = DateTime.Parse("3/10/2010")
            };
            _agent1.Bookings.Add(_booking1);
            _agent1.Bookings.Add(_booking2);
        }
      
        private async Task AddAgentAsync()
        {
            // call generic update method in Web API service to add agent and bookings
            _response = await _client.PostAsync("api/travelagent/update/",
               _agent1, new JsonMediaTypeFormatter());
      
            if (_response.IsSuccessStatusCode)
            {
                // capture newly created travel agent from service, which will include
                // database-generated Ids for each entity
                _agent1 = await _response.Content.ReadAsAsync<TravelAgent>();
                _booking1 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Karen Stevens");
                _booking2 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Dolly Parton");
      
                Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)",
                    _agent1.Name, _agent1.Bookings.Count);
            }
            else
                Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
        }
      
        private void CreateSecondAgent()
        {
            // add new agent and booking
            _agent2 = new TravelAgent {Name = "Perry Como"};
            _booking3 = new Booking
            {
                Customer = "Loretta Lynn",
                Paid = true,
                BookingDate = DateTime.Parse("3/15/2010")
            };
            _agent2.Bookings.Add(_booking3);
        }
      
        private async Task AddSecondAgentAsync()
        {
            // call generic update method in Web API service to add agent and booking
            _response = await _client.PostAsync("api/travelagent/update/",
                _agent2, new JsonMediaTypeFormatter());
      
            if (_response.IsSuccessStatusCode)
            {
                // capture newly created travel agent from service
                _agent2 = await _response.Content.ReadAsAsync<TravelAgent>();
                _booking3 = _agent2.Bookings.FirstOrDefault(x => x.Customer == "Loretta Lynn");
      
                Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)",
                    _agent2.Name, _agent2.Bookings.Count);
            }
            else
                Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
        }
      
        private void ModifyAgent()
        {
            // modify agent 2 by changing agent name and assigning booking 1 to him from agent 1
            _agent2.Name = "Perry Como, Jr.";
            _agent2.Bookings.Add(_booking1);
        }
      
        private async Task UpdateAgentAsync()
        {
            // call generic update method in Web API service to update agent 2
            _response = await _client.PostAsync("api/travelagent/update/",
                _agent2, new JsonMediaTypeFormatter());
      
            if (_response.IsSuccessStatusCode)
            {
                // capture newly created travel agent from service, which will include Ids
                _agent1 = _response.Content.ReadAsAsync<TravelAgent>().Result;
                Console.WriteLine("Successfully updated Travel Agent {0} and {1} Booking(s)",
                   _agent1.Name, _agent1.Bookings.Count);
            }
            else
                Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
        }
      
        private async Task FetchAgentsAsync()
        {
            // call Get method on service to fetch all Travel Agents and Bookings
            _response = _client.GetAsync("api/travelagent/retrieve").Result;
      
            if (_response.IsSuccessStatusCode)
            {
                // capture newly created travel agent from service, which will include Ids
                var agents = await _response.Content.ReadAsAsync<IEnumerable<TravelAgent>>();
      
                foreach (var agent in agents)
                {
                   Console.WriteLine("Travel Agent {0} has {1} Booking(s)", agent.Name, agent.Bookings.Count());
                    }
                }
                else
                    Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
            }
        }
  12. Finally, add the TravelAgent and Booking classes to this project just as we added to the service in Listing 9-12.

Following is the output of the test client from Listing 9-18:

Successfully created Travel Agent John Tate and 2 Booking(s)
Successfully created Travel Agent Perry Como and 1 Booking(s)
Successfully updated Travel Agent Perry Como, Jr. and 2 Booking(s)
Travel Agent John Tate has 1 Booking(s)
Travel Agent Perry Como, Jr. has 2 Booking(s)

How It Works

Start by running the Web API application. The Web API application contains an MVC Web Controller, which, when started, will bring up a home page. At this point, the site is running and its services are available.

Next open the console application, set a breakpoint on the first line of code in the program.cs file, and run the console application. First we establish some basic plumbing—mapping the Web API service URI and configuring the Accept Header—that will ask the Web API service to return data in a JSON format.

We then call the Cleanup action method on the TravelAgent Web API controller using the Client.DeleteAsync method exposed by the HttpClient object. Cleanup truncates the database tables to clear data from any previous operations.

Back in the client, we create a new travel agent and two bookings and then send these three new entities to the service by calling the PostAsync from the HttpClient object. If you place a breakpoint in the Update Action Method in the TravelAgent Web API controller class, you’ll see that it receives the TravelAgent object as a parameter and adds it to the TravelAgents entity in the context object. Doing so marks the object and all its related child entity objects as added and causes the context to start tracking them.

image Note  It’s worthwhile to mention that you should Add rather Attach a set of objects if there are multiple added entities with the same value in the Primary Key property (in this case, multiple Bookings with an Id = 0). If using Attach in this scenario, Entity Framework will throw an exception because of the Primary Key conflicts (multiple entities with a primary key = 0) in the non-added entities.

We next check the Id property and make a somewhat crude determination that if it is greater than 0, then this is an existing entity, and we set the entity state property to Modified by calling the Entry method on the Context object. Additionally, we set a flag entitled newParentEntity, so that we can return the correct Http Status Code later in the operation. In the event that the Id property of TravelAgent is equal to 1, then we leave the state property as Added.

We next iterate through each of the child booking objects applying the exact same logic. Upon completion, we call the SaveChanges method, which generates SQL Update statements for Modified entities and Sql Insert statements for Added entities. Then we return an Http Status Code of 201 for inserted entities and 200 for modified entities. The 200 Http Status Code informs the calling program that the operation completed successfully; while 201 informs the client that an insert operation completed successfully. When exposing REST-based services, it is a best practice to return an Http Status Code to the calling program to verify the outcome of the operation.

Subsequently in the client, we add another new travel agent and booking, using the PostAsync method to call the Update action method in the service, using the PostAsync method which again inserts each of the new objects into the database.

Next, we modify the name of the second agent and move one the bookings from the first agent to the second. This time, when we call the Update method, each of the entities has an Id property with a value greater than 1, and thus we set the entity state as modified, causing SQL Updates to be issued by the Entity Framework.

Finally, the client calls the Retrieve Action Method on the service leveraging the GetAsync method exposed by the HttpClient API. The Retrieve method is invoked, and returns all of the travel agent and booking entities. Here we simply implement eager loading with the Include() method, which returns all of the properties in each child booking entity.

Be aware that the JSON serializer will return all public properties in an entity object, even if you only project (for example, select) a subset of the properties.

In this recipe, we’ve seen that we can encapsulate Entity Framework data operations behind a Web API service. The client can consume the service by using the HttpClient object that is exposed by the Web API client API. In this example, we moved away from the Web API’s preferred HTTP verb-based dispatch and implemented more of an RPC-based routing approach. In production applications, you’d most likely want to utilize the HTTP verb-based approach, as it fits into the underlying intent of the ASP.NET Web API, which is to expose REST-based services.

In a production application, we would most likely create another layer (Visual Studio class project) to separate the Entity Framework data access code from the Web API service.

9-4. Implementing Client-Side Change Tracking with Web API

Problem

You want to leverage REST-based Web API services for database insert, delete, and update operations to an object graph while implementing a reusable client-side approach to updating entity classes. Additionally, you want to leverage the code-first approach for Entity Framework 6 to manage data access.

In this example, we emulate an n-Tier scenario where a stand-alone client application (Console Application) is calling a stand-alone website (Web API Project) that exposes REST-based services.

Note that each tier is contained in a separate Visual Studio Solution, so as to allow for easier configuring, debugging, and simulation of an n-Tier application.

Solution

Let’s say that you have a model like the one shown in Figure 9-4.

9781430257882_Fig09-04.jpg

Figure 9-4. A Customer and Phone Numbers model

Our model represents Customers and their corresponding Phone Numbers. We want to put the model and database code behind a Web API service so that any client that consumes HTTP can insert, update, and delete orders. To create the service, perform the following steps:

  1. Create a new ASP.NET MVC 4 Web Application project, selecting the Web API template from the Project Templates wizard. Name the project Recipe4.Service.
  2. Add a new Web API Controller to the project entitled CustomerController.
  3. Next, from Listing 9-19 add the entity base class entitled BaseEntity and the enum type entitled TrackingState. The base class extends each entity, adding a TrackingState property that the client is required to set when manipulating entity objects. The TrackingState property is driven from the TrackingState enum. Note that the TrackingState is not persisted to the database. Creating our own internal tracking state enum class lets us keep the client free of Entity Framework dependencies that would be required if we were to expose the Entity Framework tracking states to the client. In the DbContext file, note how we will instruct Entity Framework not to map the TrackingState property to the underlying database tables in the OnModelCreating method.

    Listing 9-19.  Entity Base Class and TrackingState Enum Type

    public abstract class BaseEntity
    {
        protected BaseEntity()
        {
            TrackingState = TrackingState.Nochange;
        }
      
        public TrackingState TrackingState { get; set; }
    }
     
    public enum TrackingState
    {
        Nochange,
        Add,
        Update,
        Remove,
    }
  4. Next, from Listing 9-20, add the Customer and PhoneNumber entity classes.

    Listing 9-20.  Customer and Phone Entity Classes

    public class Customer : BaseEntity
    {
        public int CustomerId { get; set; }
        public string Name { get; set; }
        public string Company { get; set; }
      
        public virtual ICollection<Phone> Phones { get; set; }
    }
     
    public class Phone : BaseEntity
    {
        public int PhoneId { get; set; }
        public string Number { get; set; }
        public string PhoneType { get; set; }
        public int CustomerId { get; set; }
      
        public virtual Customer Customer { get; set; }
    }
  5. Add a reference in the Recipe4.Service project to the Entity Framework 6 libraries. Leveraging the NuGet Package Manager does this best.  Right-click on Reference, and select Manage NuGet Packages. From the Online tab, locate and install the Entity Framework 6 package.
  6. Then add a new class entitled Recipe4Context, and add the code from Listing 9-21 to it, ensuring that the class derives from the Entity Framework DbContext class. Note closely how we leverage a new Entity Framework 6 features entitled “Configuring Unmapped Base Types.” In Listing 9-21, we define a convention that instructs each entity class to “ignore” (i.e., not map to the underlying database) the TrackingState property from the BaseEntity base class which we include only to track the state of the disconnected entities in operations that cross service boundaries.

    image Note  Rowan Martin, Microsoft Program Manager for the Entity Framework Team has published a helpful blog post about Configuring Unmapped Base Types: http://romiller.com/2013/01/29/ef6-code-first-configuring-unmapped-base-types/. Be certain to check out Rowan’s other outstanding blog posts on the Entity Framework.

    Listing 9-21.  Context Class

    public class Recipe4Context : DbContext
    {
        public Recipe4Context() : base("Recipe4ConnectionString") { }
      
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Phone> Phones { get; set; }
      
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Do not persist TrackingState property to data store
            // This property is used internally to track state of
            // disconnected entities across service boundaries.
            // Leverage the Custom Code First Conventions features from Entity Framework 6.
            // Define a convention that performs a configuration for every entity
            // that derives from a base entity class.
            modelBuilder.Types<BaseEntity>().Configure(x => x.Ignore(y => y.TrackingState));
            modelBuilder.Entity<Customer>().ToTable("Chapter9.Customer");
            modelBuilder.Entity<Phone>().ToTable("Chapter9.Phone");
        }
    }
  7. Next, from Listing 9-22, add the Recipe4ConnectionString connection string to the Web.Config file under the ConnectionStrings section.

    Listing 9-22.  Connection string for the Recipe1 Web API Service

    <connectionStrings>
      <add name="Recipe4ConnectionString"
        connectionString="Data Source=.;
           Initial Catalog=EFRecipes;
           Integrated Security=True;
           MultipleActiveResultSets=True"
        providerName="System.Data.SqlClient" />
    </connectionStrings>
  8. Then add the code in Listing 9-23 to the Application_Start method in the Global.asax file. This code will disable the Entity Framework Model Compatibility check and instruct the JSON serializer to ignore the self-referencing loop caused by navigation properties being bidirectional between Customer and PhoneNumber.

    Listing 9-23.  Disable the Entity Framework Model Compatibility Check

     protected void Application_Start()
    {
                // Disable Entity Framework Model Compatibilty
                Database.SetInitializer<Recipe1Context>(null);
     
                // The bidirectional navigation properties between related entities
                // create a self-referencing loop that breaks Web API's effort to
                // serialize the objects as JSON. By default, Json.NET is configured
                // to error when a reference loop is detected. To resolve problem,
                // simply configure JSON serializer to ignore self-referencing loops.
                GlobalConfiguration.Configuration.Formatters.JsonFormatter
                    .SerializerSettings.ReferenceLoopHandling =
                        Newtonsoft.Json.ReferenceLoopHandling.Ignore;
                ...
    }
  9. Next add a class entitled EntityStateFactory, and add the code from Listing 9-24 to it. The factory will translate the TrackingState enum values exposed to the client to Entity Framework state value required by the change tracking components.

    Listing 9-24.  Customer Web API Controller

    public static EntityState Set(TrackingState trackingState)
    {
        switch (trackingState)
        {
            case TrackingState.Add:
                return EntityState.Added;
            case TrackingState.Update:
                return EntityState.Modified;
            case TrackingState.Remove:
                return EntityState.Deleted;
            default:
                return EntityState.Unchanged;
        }
    }

    Finally, replace the code in the CustomerController with that from Listing 9-25.

    Listing 9-25.  Customer Web API Controller

    public class CustomerController : ApiController
    {
        // GET api/customer
        public IEnumerable<Customer> Get()
        {
            using (var context = new Recipe4Context())
            {
                return context.Customers.Include(x => x.Phones).ToList();
            }
        }
      
        // GET api/customer/5
        public Customer Get(int id)
        {
            using (var context = new Recipe4Context())
            {
                return context.Customers.Include(x => x.Phones)
                     .FirstOrDefault(x => x.CustomerId == id);
            }
        }
      
        [ActionName("Update")]
        public HttpResponseMessage UpdateCustomer(Customer customer)
        {
            using (var context = new Recipe4Context())
            {
                // Add object graph to context setting default state of 'Added'.
                // Adding parent to context automatically attaches entire graph
                // (parent and child entities) to context and sets state to 'Added'
                // for all entities.
                context.Customers.Add(customer);
      
                foreach (var entry in context.ChangeTracker.Entries<BaseEntity>())
                {
                    entry.State = EntityStateFactory.Set(entry.Entity.TrackingState);
      
                    if (entry.State == EntityState.Modified)
                    {
                        // For entity updates, we fetch a current copy of the entity
                        // from the database and assign the values to the orginal values
                        // property from the Entry object. OriginalValues wrap a dictionary
                        // that represents the values of the entity before applying changes.
                        // The Entity Framework change tracker will detect
                        // differences between the current and original values and mark
                        // each property and the entity as modified. Start by setting
                        // the state for the entity as 'Unchanged'.
                        entry.State = EntityState.Unchanged;
                        var databaseValues = entry.GetDatabaseValues();
                        entry.OriginalValues.SetValues(databaseValues);
                    }
                }
      
                context.SaveChanges();
            }
      
            return Request.CreateResponse(HttpStatusCode.OK, customer);
        }
      
        [HttpDelete]
        [ActionName("Cleanup")]
        public HttpResponseMessage Cleanup()
        {
            using (var context = new Recipe4Context())
            {
                context.Database.ExecuteSqlCommand("delete from chapter9.phone");
                context.Database.ExecuteSqlCommand("delete from chapter9.customer");
      
                return Request.CreateResponse(HttpStatusCode.OK);
            }
        }
    }

    Next we create the Visual Studio solution that will contain the client project that will consume the Web API service.

  10. Create a new Visual Studio solution that contains a Console application entitled Recipe3.Client.
  11. Replace the code in the program.cs file with that from Listing 9-26.

    Listing 9-26.  Our Windows Console Application That Serves as Our Test Client

    internal class Program
    {
        private HttpClient _client;
        private Customer _bush, _obama;
        private Phone _whiteHousePhone, _bushMobilePhone, _obamaMobilePhone;
        private HttpResponseMessage _response;
      
        private static void Main()
        {
            Task t = Run();
            t.Wait();
            Console.WriteLine(" Press <enter> to continue...");
            Console.ReadLine();
        }
      
        private static async Task Run()
        {
            var program = new Program();
            program.ServiceSetup();
            // do not proceed until clean-up completes
            await program.CleanupAsync();
            program.CreateFirstCustomer();
            // do not proceed until customer is added
            await program.AddCustomerAsync();
            program.CreateSecondCustomer();
            // do not proceed until customer is added
            await program.AddSecondCustomerAsync();
            // do not proceed until customer is removed
            await program.RemoveFirstCustomerAsync();
            // do not proceed until customers are fetched
            await program.FetchCustomersAsync();
        }
      
        private void ServiceSetup()
        {
            // set up infrastructure for Web API call
            _client = new HttpClient {BaseAddress = new Uri("http://localhost:62799/")};
      
            // add Accept Header to request Web API content negotiation to return resource in JSON format
            _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
      
        private async Task CleanupAsync()
        {
            // call the cleanup method from the service
            _response = await _client.DeleteAsync("api/customer/cleanup/");
        }
      
        private void CreateFirstCustomer()
        {
            // create customer #1 and two phone numbers
            _bush = new Customer
            {
                Name = "George Bush",
                Company = "Ex President",
                // set tracking state to 'Add' to generate a SQL Insert statement
                TrackingState = TrackingState.Add,
            };
      
            _whiteHousePhone = new Phone
            {
                Number = "212 222-2222",
                PhoneType = "White House Red Phone",
                // set tracking state to 'Add' to generate a SQL Insert statement
                TrackingState = TrackingState.Add,
            };
      
            _bushMobilePhone = new Phone
            {
                Number = "212 333-3333",
                PhoneType = "Bush Mobile Phone",
                // set tracking state to 'Add' to generate a SQL Insert statement
                TrackingState = TrackingState.Add,
            };
      
            _bush.Phones.Add(_whiteHousePhone);
            _bush.Phones.Add(_bushMobilePhone);
        }
      
        private async Task AddCustomerAsync()
        {
            // construct call to invoke UpdateCustomer action method in Web API service
            _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter());
      
            if (_response.IsSuccessStatusCode)
            {
                // capture newly created customer entity from service, which will include
                // database-generated Ids for all entities
                _bush = await _response.Content.ReadAsAsync<Customer>();
                _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId);
                _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _bush.CustomerId);
      
                Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)",
                    _bush.Name, _bush.Phones.Count);
                foreach (var phoneType in _bush.Phones)
                {
                    Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType);
                }
            }
            else
               Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
        }
      
        private void CreateSecondCustomer()
        {
            // create customer #2 and phone numbers
            _obama = new Customer
            {
                Name = "Barack Obama",
                Company = "President",
                // set tracking state to 'Add' to generate a SQL Insert statement
                TrackingState = TrackingState.Add,
            };
      
            _obamaMobilePhone = new Phone
            {
                Number = "212 444-4444",
                PhoneType = "Obama Mobile Phone",
                // set tracking state to 'Add' to generate a SQL Insert statement
                TrackingState = TrackingState.Add,
            };
      
            // set tracking state to 'Modifed' to generate a SQL Update statement
            _whiteHousePhone.TrackingState = TrackingState.Update;
      
            _obama.Phones.Add(_obamaMobilePhone);
            _obama.Phones.Add(_whiteHousePhone);
        }
      
        private async Task AddSecondCustomerAsync()
        {
            // construct call to invoke UpdateCustomer action method in Web API service
            _response = await _client.PostAsync("api/customer/updatecustomer/",
                _obama, new JsonMediaTypeFormatter());
      
            if (_response.IsSuccessStatusCode)
            {
                // capture newly created customer entity from service, which will include
                // database-generated Ids for all entities
                _obama = await _response.Content.ReadAsAsync<Customer>();
                _whiteHousePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId);
                _bushMobilePhone = _bush.Phones.FirstOrDefault(x => x.CustomerId == _obama.CustomerId);
      
                Console.WriteLine("Successfully created Customer {0} and {1} Phone Numbers(s)",
                    _obama.Name, _obama.Phones.Count);
      
                foreach (var phoneType in _obama.Phones)
                {
                    Console.WriteLine("Added Phone Type: {0}", phoneType.PhoneType);
                }
            }
            else
                Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
        }
      
        private async Task RemoveFirstCustomerAsync()
        {
            // remove George Bush from underlying data store.
            // first, fetch George Bush entity, demonstrating a call to the
            // get action method on the service while passing a parameter
            var query = "api/customer/" + _bush.CustomerId;
            _response = _client.GetAsync(query).Result;
      
            if (_response.IsSuccessStatusCode)
            {
                _bush = await _response.Content.ReadAsAsync<Customer>();
      
                // set tracking state to 'Remove' to generate a SQL Delete statement
                _bush.TrackingState = TrackingState.Remove;
      
                // must also remove bush's mobile number -- must delete child before removing parent
                foreach (var phoneType in _bush.Phones)
                {
                    // set tracking state to 'Remove' to generate a SQL Delete statement
                    phoneType.TrackingState = TrackingState.Remove;
                }
      
                // construct call to remove Bush from underlying database table
                _response = await _client.PostAsync("api/customer/updatecustomer/", _bush, new JsonMediaTypeFormatter());
      
                if (_response.IsSuccessStatusCode)
                {
                    Console.WriteLine("Removed {0} from database", _bush.Name);
                    foreach (var phoneType in _bush.Phones)
                    {
                        Console.WriteLine("Remove {0} from data store", phoneType.PhoneType);
                    }
                }
                else
                    Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
            }
            else
            {
                Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
            }
        }
      
        private async Task FetchCustomersAsync()
        {
      
            // finally, return remaining customers from underlying data store
            _response = await _client.GetAsync("api/customer/");
      
            if (_response.IsSuccessStatusCode)
            {
                var customers = await _response.Content.ReadAsAsync<IEnumerable<Customer>>();
      
                foreach (var customer in customers)
                {
                    Console.WriteLine("Customer {0} has {1} Phone Numbers(s)",
                        customer.Name, customer.Phones.Count());
                    foreach (var phoneType in customer.Phones)
                    {
                        Console.WriteLine("Phone Type: {0}", phoneType.PhoneType);
                    }
                }
            }
            else
            {
                Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
            }
        }
    }
  12. Finally, add the same Customer, Phone, BaseEntity, and TrackingState classes that we added to service in Listings 9-19 and 9-20.

Following is the output of our test client from Listing 9-26:

Successfully created Customer Geroge Bush and 2 Phone Numbers(s)
Added Phone Type: White House Red Phone
Added Phone Type: Bush Mobile Phone
Successfully created Customer Barrack Obama and 2 Phone Numbers(s)
Added Phone Type: Obama Mobile Phone
Added Phone Type: White House Red Phone
Removed Geroge Bush from database
Remove Bush Mobile Phone from data store
Customer Barrack Obama has 2 Phone Numbers(s)
Phone Type: White House Red Phone
Phone Type: Obama Mobile Phone

How It Works

Start by running the Web API application. The Web API application contains an MVC Web Controller that, when started, will bring up a home page. At this point, the site is running and its services are available.

Next open the console application, set a breakpoint on the first line of code in the program.cs file, and run the console application. We first establish some basic plumbing—mapping the Web API service URI and configuring the Accept Header—that will ask the Web API service to return the data in a JSON format.

We then call the Cleanup action method on the Customer Web API controller using the Client.DeleteAsync method exposed by the HttpClient object. This call invokes the Cleanup action method in the service and truncates data from the database tables for any previous operations.

Back in the client, we create a new customer and two phone number objects. Note how we explicitly set the TrackingState property for each entity in the client to instruct the Entity Framework Change Tracking Components of the SQL operation required for each entity.

We then invoke the UpdateCustomer action method from the service by making a call to the PostAsync method from the HttpClient object. If you place a breakpoint in the UpdateCustomer Action Method in the Customer Web API controller class, you’ll see that it receives the Customer object as a parameter and immediately adds it to the context object. Doing so marks the object graph as added, and it causes the context to start tracking it.

Interestingly, we next hook into the underlying DbChangeTracker, which is exposed as a property of the context object. DbChangeTracker exposes a generic IEnumerable type of <DbEntityEntry> entitled Entries. We simply assign the base EntityType to it. Doing so allows us to enumerate through each of the entities in the context that are of type BaseEntity (the base type from which our entity classes derive in this recipe). For each iteration, we make a call to the EntityStateFactory to translate our internal TrackingState enum value to a valid EntityState value used by Entity Framework to drive change tracking. If the client set the TrackingState to Modified, we do some additional processing. We set the state of the entity (from Modified) to Unchanged and call the GetDatabaseValues method on the Entry object, which returns the current values for the entity from the underlying data store. We then assign these current values to the OriginalValues collection in the Entry object. Under the hood, the Entity Framework change-tracking engine detects any differences between the original and submitted values, and it marks those individual properties as Modified and the entity as Modified. The subsequent SaveChanges operation will then only update those properties that we changed in the client—not all of the properties in the entity.

Back in the client, we demonstrate adding, modifying, and deleting entity objects by setting the TrackingState. The UpdateCustomer method in the service simply translates the TrackingState values to Entity Framework state properties and submits the objects to the change-tracking engine for the correct SQL operation.

In this recipe, we’ve seen that we can encapsulate Entity Framework data operations behind a Web API service. The client can consume the service by using the HttpClient object that is exposed by the Web API client API. By requiring each entity object to derive from a base entity type, we can expose a TrackingState value that the client can set to communicate the needed SQL operation to the Entity Framework change-tracking engine.

In a production application, we would most likely create another layer (Visual Studio class project) to separate the Entity Framework data access code from the Web API service. More importantly, it would not be difficult to take the client-side tracking approach used in this recipe and implement it using generic types. Doing so would allow us to reuse the base functionality across all of our entity types, thus reducing large amounts of redundant code.

9-5. Deleting an Entity When Disconnected

Problem

You have an object that you have retrieved from a WCF service and you want to mark it for deletion.

Solution

Suppose that you have a model like the one shown in Figure 9-5.

9781430257882_Fig09-05.jpg

Figure 9-5. A model for payments on invoices

Our model represents payments on invoices. In our application, we have implemented a WCF service to handle the database interactions from a client. We want to delete an object, in our case a Payment entity, using the service. To keep the solution as simple as possible, we’ll build a WCF service library and define the model inside of it by doing the following:

  1. Create a WCF Service Library by right-clicking the solution and selecting Add New Project. Select WCF arrow.jpg WCF Service Library. Name the WCF library Recipe5.
  2. Right-click the Recipe5 project, and select Add New Item. Select Data arrow.jpg ADO.NET Entity Data Model. Use the wizard to add a model with the Invoice and Payment tables. For simplicity, we’ve removed the Payments navigation property on the Invoice entity. (Right-click on the Payments navigation property in the Invoice entity in the Entity Framework designer, and click Delete From Model.) Right-click the TimeStamp property in the Payment entity, select Properties, and set its Concurrency Mode to Fixed. Doing so will engage the TimeStamp property in concurrency control, sending the value as part of the WHERE clauses in all subsequent SQL update and delete operations.
  3. In the IService1.cs file, change the service definition as shown in Listing 9-27.

    Listing 9-27.  The Service Contract for Our WCF Service

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Payment InsertPayment();
     
        [OperationContract]
        void DeletePayment(Payment payment);
    }
  4. In the Service1.cs file, implement the service as shown in Listing 9-28.

    Listing 9-28.  The Implementation of Our Service Contract

    public class Service1 : IService1
    {
        public Payment InsertPayment()
        {
            using (var context = new EFRecipesEntities())
            {
                // delete the previous test data
                context.Database.ExecuteSqlCommand("delete from chapter9.payment");
                context.Database.ExecuteSqlCommand("delete from chapter9.invoice");
     
                var payment = new Payment { Amount = 99.95M, Invoice =
                                  new Invoice { Description = "Auto Repair" } };
                context.Payments.Add(payment);
                context.SaveChanges();
                return payment;
            }
        }
     
        public void DeletePayment(Payment payment)
        {
            using (var context = new EFRecipesEntities())
            {
                context.Entry(payment).State = EntityState.Deleted;
                context.SaveChanges();
            }
        }
    }
  5. To test our service, we’ll need a client. Add a new Windows Console Application project to the solution. Use the code in Listing 9-29 for the client. Add a service reference to the client by right-clicking the client project and selecting Add Service Reference. You may need to right-click the service project and select Debug arrow.jpg Start Instance to start an instance of your service before you can add a service reference in the client.

    Listing 9-29.  A Simple Console Application to Test Our WCF Service

    class Program
    {
        static void Main()
        {
            var client = new Service1Client();
            var payment = client.InsertPayment();
            client.DeletePayment(payment);
        }
    }

If you set a breakpoint on the first line in the Main() method of the client and debug the application, you can step through the insertion and deletion of a Payment entity.

How It Works

In this recipe, we demonstrate a common pattern for updating disconnected entities where the client is consuming WCF or Web API services that expose data from Entity Framework.

In the client, we use the InsertPayment() method to insert a new payment into the database. The method returns the payment that was inserted. The payment that is returned to the client is disconnected from the DbContext. In fact, in a situation such as this, the context object may be in a different process space or on an entirely different computer.

We use the DeletePayment() method to delete the Payment entity from the database. In the implementation of this method (see Listing 9-28), we call the Entry() method from the DbContext object passing in an argument of payment. We then set the State property for this entity to EntityState.Deleted, which marks the object for deletion. SaveChanges() deletes the payment from the database.

The payment object that we attached for deletion had all its properties set as they were when the object was inserted into the database. However, because we’re using foreign key association, only the entity key, concurrency property, and TimeStamp property are needed for Entity Framework to generate the appropriate where clause to delete the entity. The one exception to this rule is when your POCO class has one or more properties that are complex types. Because complex types are considered structural parts of an entity, they cannot be null. To keep things simple, you could simply create a dummy instance of the complex type, as Entity Framework is building its SQL Delete statement from the entity key and concurrency property only. If you leave the complex type property null, SaveChanges() will throw an exception.

If you are using an independent association in which the multiplicity of the related entity is one or 0..1, then Entity Framework requires the entity keys of those references to be set correctly in order to generate the appropriate where clause of an update or delete statement. In our example, if we had an independent association between Invoice and Payment, we would need to set the Invoice navigation property to an instance of Invoice with the correct value for the InvoiceId property. The resulting where clause would include the PaymentId, TimeStamp, and InvoiceId.

image Note  When implementing an N-Tier architecture with Entity Framework, serious consideration should be given to using the Foreign Key Association approach for related entities. The Independent Association approach is difficult to implement and can make your code quite complex. For a great explanation of these approaches, including their benefits and drawbacks, check out the following blog post from Arthur Vickers, Developer on the Entity Framework Team: whats-the-deal-with-mapping-foreign-keys-using-the-entity-framework. Be certain to check out Arthur’s other outstanding blog posts on the Entity Framework.

If your entity object contains several independent associations, setting all of them can quickly become tedious. You might find it simpler just to retrieve the instance from the database and mark it for deletion. This makes your code a little simpler, but when you retrieve the object from the database, Entity Framework will rewrite the query to bring in all of the relationships that are one or 0..1, unless, of course, you are using the NoTracking context option. If this Recipe were implementing the Independent Association approach, when we load the Payment entity prior to marking for deletion, Entity Framework would create an object state entry for the Payment entity and a relationship entry for the relationship between Payment and Invoice. When we marked the Payment entity for deletion, Entity Framework would also mark the relationship entry for deletion. Like previously, the resulting where clause would include the PaymentId, TimeStamp, and InvoiceId.

Another option for deleting entities in independent associations is to eagerly load the related entities and transport the entire object graph back to the WCF or Web API service for deletion. In this example, we could eagerly load the related Invoice entity with the Payment entity. If we were to delete the Payment entity, we could send back the graph containing both entities to the service. But, be forewarned that this approach consumes more network bandwidth and processing time for serialization, so the cost may outweigh the benefit of more clarity in the code.

9-6. Managing Concurrency When Disconnected

Problem

You want to make sure that changes made on an entity by a WCF client are applied only if the concurrency token has not changed.

Solution

Let’s suppose that you have a model like the one shown in Figure 9-6.

9781430257882_Fig09-06.jpg

Figure 9-6. Our model with a single Order entity

We want to update an order using a WCF service while guaranteeing that the order we’re updating has not changed since the last time we retrieved the order. We’ll show two slightly different ways to handle this. In both approaches, we use a concurrency column, in our case, the TimeStamp column.

  1. Create a WCF Service Library by right-clicking the solution and selecting Add New Project. Select WCF arrow.jpg WCF Service Library. Name the project Recipe6.
  2. Right-click the project, and select Add New Item. Select Data arrow.jpg ADO.NET Entity Data Model. Use the wizard to add a model with the Order table. In the Entity Framework designer, right-click the TimeStamp property, select Properties, and set its Concurrency Mode to Fixed.
  3. In the IService1.cs file, change the service definition as shown in Listing 9-30.

    Listing 9-30.  Our WCF Service Contract

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Order InsertOrder();
     
        [OperationContract]
        void UpdateOrderWithoutRetrieving(Order order);
     
        [OperationContract]
        void UpdateOrderByRetrieving(Order order);
    }
  4. In the Service1.cs file, implement the service as shown in Listing 9-31.

    Listing 9-31.  The Implementation of Our Service Contract

    public class Service1 : IService1
    {
        public Order InsertOrder()
        {
            using (var context = new EFRecipesEntities())
            {
                // remove previous test data
                context.Database.ExecuteSqlCommand("delete from chapter9.[order]");
     
                var order = new Order { Product = "Camping Tent",
                                  Quantity = 3, Status = "Received" };
                context.Orders.Add(order);
                context.SaveChanges();
                return order;
            }
        }
     
        public void UpdateOrderWithoutRetrieving(Order order)
        {
            using (var context = new EFRecipesEntities())
            {
                    try
                    {
                        context.Orders.Attach(order);
                        if (order.Status == "Received")
                        {
                            context.Entry(order).Property(x => x.Quantity).IsModified = true;
                            context.SaveChanges();
                        }
                    }
                    catch (OptimisticConcurrencyException ex)
                    {
                        // Handle OptimisticConcurrencyException
                        
                    }
            }
        }
     
        public void UpdateOrderByRetrieving(Order order)
        {
            using (var context = new EFRecipesEntities())
            {
                // fetch current entity from database
                var dbOrder = context.Orders
                               .Single(o => o.OrderId == order.OrderId);
                if (dbOrder != null &&
                    // execute concurrency check
                    StructuralComparisons.StructuralEqualityComparer.Equals(order.TimeStamp, dbOrder.TimeStamp))
                {
                    dbOrder.Quantity = order.Quantity;
                    context.SaveChanges();
                }
          else
          {
             // Add code to handle concurrency issue
          }
            }
        }
    }
  5. To test our service, we’ll need a client. Add a new Windows Console Application project to the solution. Use the code in Listing 9-32 for the client. Add a service reference to the client by right-clicking the client project and selecting Add Service Reference. You may need to right-click the service project and select Debug arrow.jpg Start Instance to start an instance of your service before you can add a service reference in the client.

    Listing 9-32.  The Client We Use to Test Our WCF Service

    class Program
    {
        static void Main(string[] args)
        {
            var service = new Service1Client();
            var order = service.InsertOrder();
            order.Quantity = 5;
            service.UpdateOrderWithoutRetrieving(order);
            order = service.InsertOrder();
            order.Quantity = 3;
            service.UpdateOrderByRetrieving(order);
        }
    }

If you set a breakpoint on the first line in the Main() method of the client and debug the application, you can step through inserting the order and updating the order using both methods.

How It Works

Our InsertOrder() method (see Listing 9-31) deletes any previous test data, inserts a new order, and returns the order. The order returned has both the database generated OrderId and TimeStamp properties. In our client, we use two slightly different approaches to update this order.

In the first approach, UpdateOrderWithoutRetrieving(), we Attach() the order from the client and check whether the order status is Received, and, if it is, we mark the entity’s Quantity property as modified and call SaveChanges(). Entity Framework will generate an update statement setting the new quantity with a where clause that includes both the OrderId and the TimeStamp values from the Order entity. If the TimeStamp value has changed by some intermediate update to the database, this update will fail. To capture and handle such a concurrency exception, we wrap the operation with Try/Catch construct, trap for an OptimisticConcurrencyException, and handle the exception. This ensures that the Order entity we are updating has not been modified between the time we obtained it from the InsertOrder() method and the time we updated it in the database. Note in the example how all entity properties are updated, whether they have changed or not.

Alternately, you could explicitly check the concurrency of an entity before performing an update. Here you could retrieve the target entity from the database and manually compare the TimeStamp properties to determine whether an intervening change has occurred. This approach is illustrated in Listing 9-31 with a fresh order by calling the UpdateOrderByRetrieving() method. Although not foolproof (the order could be changed by another client between the time you retrieve the order from the database, compare TimeStamp values, and call SaveChanges()), this approach does provide valuable insight into what properties or associations have changed on an entity. Although not efficient as the first approach, this method might be useful if the object graph or entities are large or complex.

9-7. Serializing Proxies in a WCF Service

Problem

You have a dynamic proxy object returned from a query. You want to serialize the proxy as a plain old CLR object. When Implementing POCO-based entity objects (Plain-Old CLR Objects), Entity Framework automatically generates a dynamically generated derived type at runtime, known as a dynamic proxy object, for each POCO entity object. The proxy object overrides many of the virtual properties of the POCO class to inject hooks for performing actions such as change tracking and the lazy loading of related entities.

Solution

Let’s suppose that you have a model like the one shown in Figure 9-7.

9781430257882_Fig09-07.jpg

Figure 9-7. A model with a Client entity

We’ll use the ProxyDataContractResolver class to deserialize a proxy object on the server to a POCO object on the WCF client. Do the following:

  1. Create a new WCF Service Application. Add an ADO.NET Entity Data Model with the Client table. The model should look like the one shown in Figure 9-7.
  2. Open the Client POCO class that Entity Framework generated, and add the virtual keyword to each property, as shown in Listing 9-33. Doing so will cause Entity Framework to generate dynamic proxy classes.

    image Note  Keep in mind that if you make any changes to the EDMX file, Entity Framework will automatically regenerate your underlying classes and overwrite your changes from Step #2. You could repeat your changes or even consider modifying the underlying T4 template that generates the entity code.

    Listing 9-33.  Our Client POCO Class and Our Object Vontext

    public class Client
    {
        public virtual int ClientId { get; set; }
        public virtual string Name { get; set; }
        public virtual string Email { get; set; }
    }
  3. We need the DataContractSerializer to use a ProxyDataContractResolver class to transform the client proxy to the client entity for the WCF service’s client. For this, we’ll create an operation behavior attribute and apply the attribute on the GetClient() service method. Add the code in Listing 9-34 to create the new attribute. Keep in mind that the ProxyDataContractResolver class resides in the Entity Framework namespace.

    Listing 9-34.  Our Custom Operation Behavior Attribute

    using System.ServiceModel.Description;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Dispatcher;
    using System.Data.Objects;
     
    namespace Recipe8
    {
        public class ApplyProxyDataContractResolverAttribute : Attribute,
                     IOperationBehavior
        {
            public void AddBindingParameters(OperationDescription description,
                                              BindingParameterCollection parameters)
            {
            }
     
            public void ApplyClientBehavior(OperationDescription description,
                                             ClientOperation proxy)
            {
                DataContractSerializerOperationBehavior
                    dataContractSerializerOperationBehavior =
                          description.Behaviors
                           .Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver =
                          new ProxyDataContractResolver();
            }
     
            public void ApplyDispatchBehavior(OperationDescription description,
                        DispatchOperation dispatch)
            {
                DataContractSerializerOperationBehavior
                    dataContractSerializerOperationBehavior =
                          description.Behaviors
                           .Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver =
                          new ProxyDataContractResolver();
            }
     
            public void Validate(OperationDescription description)
            {
            }
        }
    }
  4. Change the IService1.cs interface using the code in Listing 9-35.

    Listing 9-35.  Our IService1 Interface Definition, Which Replaces the Code in IService1.cs

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        void InsertTestRecord();
     
        [OperationContract]
        Client GetClient();
     
        [OperationContract]
        void Update(Client client);
    }
  5. Change the implementation of the IService1 interface in the IService1.svc.cs file with the code shown in Listing 9-36.

    Listing 9-36.  The Implementation of the IService1 Interface, Which Replaces the Code in IService1.svc.cs

    public class Client
    {
        [ApplyProxyDataContractResolver]
        public Client GetClient()
        {
            using (var context = new EFRecipesEntities())
            {
                context.Cofiguration.LazyLoadingEnabled = false;
                return context.Clients.Single();
            }
        }
     
        public void Update(Client client)
        {
            using (var context = new EFRecipesEntities())
            {
                context.Entry(client).State  =
                       EntityState.Modified;
                context.SaveChanges();
            }
        }
     
        public void InsertTestRecord()
        {
            using (var context = new EFRecipesEntities())
            {
                // delete previous test data
                context.ExecuteSqlCommand("delete from chapter9.client");
     
                // insert new test data
                context.ExecuteStoreCommand(@"insert into
                          chapter9.client(Name, Email)
                          values ('Jerry Jones','[email protected]')");
            }
        }
    }
  6. Add a Windows Console Application to the solution. This will be our test client. Use the code shown in Listing 9-37 to implement our test client. Add a service reference to our WCF service.

    Listing 9-37.  Our Windows console application test client

    using Recipe8Client.ServiceReference1;
     
    namespace Recipe8Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (var serviceClient = new Service1Client())
                {
                    serviceClient.InsertTestRecord();
                    var client = serviceClient.GetClient();
                    Console.WriteLine("Client is: {0} at {1}",
                                        client.Name, client.Email);
                    client.Name = "Alex Park";
                    client.Email = "[email protected]";
                    serviceClient.Update(client);
                    client = serviceClient.GetClient();
                    Console.WriteLine("Client changed to: {0} at {1}",
                                        client.Name, client.Email);
                }
            }
        }
    }

Following is the output of our test client:

Client is: Jerry Jones at [email protected]
Client changed to: Alex Park at [email protected]

How It Works

Microsoft recommends using POCO objects with WCF to simplify serialization of the entity object. However, if your application is using POCO objects with changed-based notification (you have marked properties as virtual and navigation property collections are of type ICollection), then Entity Framework will create dynamic proxies for entities returned from queries.

There are two problems with dynamic proxies and WCF. The first problem has to do with the serialization of the proxy. The DataContractSerializer can only serialize and deserialize known types, such as the Client entity in our example. However, as Entity Framework generates a dynamic proxy class for the Client entity, we need to serialize the proxy class, not the Client. Here is where DataContractResolver comes to the rescue. It can map one type to another during serialization. ProxyDataContractResolver derives from DataContractResolver and maps proxy types to POCO classes, such as our Client entity. To use the ProxyDataContractResolver, we created an attribute (see Listing 9-34) to resolve proxies into POCO classes. We applied this attribute to the GetClient() method in Listing 9-36. This causes the dynamic proxy for the Client entity returned by the GetClient() to be correctly serialized for its journey to the user of the WCF service.

The second problem with dynamic proxies and WCF has to do with lazy loading. When the DataContractSerializer serializes the entity, it accesses each of the properties of the entity that would trigger lazy loading of navigation properties. This, of course, is not what we want. To prevent this, we explicitly turned off lazy loading in Listing 9-36.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset