NTier with Entity Framework (EF) 4 Beta 1 – (VS2010 / .Net 4.0 Beta 1)

Introduction

My blog has been feeling very neglected after my move to the US and I thought with the recent release of Visual Studio 2010 / .Net Framework 4.0 Beta 1 it would be a good time to get back into a regular blogging rhythm.

So for the first post I thought I’d run through a simple demo of the new N-Tier focused APIs in the ADO.Net Entity Framework bits (EF4) that are included in Beta 1 of Visual Studio 2010 / .Net Framework 4.0.

For this demo we are going to build a simple WCF service that allows distributed clients to persist changes to a simple object graph with an Order as it’s root.

Before we dive in there are a couple of things to mention;

  • The general approach I’m going to demo is for people who want to have complete control over their entities and don’t want to share behaviour between the server and distributed clients. On the other hand if you are after something that “just works out of the box” then you should check out Self Tracking Entities.
  • There is still one piece of the EF4 N-Tier story to come in the next stage of the release cycle, the availability of a new “Foreign Key Association” type. Apart from the obvious use cases for exposing FKs this new type of association also has some great side effects for processing changes on the server in an N-Tier scenario even when you don’t want to expose actual FK properties to the client. I’ll touch on this more during the post.

Building a Model

I started by creating a new WCF Service;

  1. File -> New -> Project
  2.  WCF -> WCF Service Library

The next step is to put together the EF model we are going to use;

  1. Project -> Add New Item
  2. Data -> ADO.Net Entity Data Model

Now in Beta1 you have a couple of options here;

  • Start at the database and generate your mode (Just follow the wizard)
  • Start in the designer with an empty model and then it can generate a database create script for you (right click on the design surface -> “Generate Database Script from Model”)

I already had this database handy so I just generated the model shown below, I made a couple of tweeks to the generated model;

  • Mark the “ConcurrencyToken” properties to be treated as such
    (Select the property on the design surface and in the properties tab (F4) set ConcurrencyMode to “Fixed”)
  • Remove Customer.Orders and Product.OrderLines navigation properties, in Beta 1 there is no way to do this in the designer so you need to open the edmx file in a text/xml editor and remove the navigation properties from the CSDL section.

Swapping Generated Entities for POCO Classes

One of the new features in EF4 is the ability to use POCO classes as entities, I have a few options here again;

  • I could just completely switch of code generation and write everything by hand
  • Because code generation now uses T4 templates I could write/modify a template to generate classes the way I want
  • I can also combine the above two points and modify the default T4 template to only generate the derived context and then write my own classes for the entities.

I’m going with the third option as I want absolute control over my entities but I also like having the helper methods/properties on the derived context;

  1. Right click on the design surface
  2. Select “Add New Artifact Generation Item”
  3. Select Code -> “ADO.Net EntityObject Generator” (I’m calling mine ContextOnly.tt
    This gives us a local copy of the default code generation template
  4. Now just remove the sections for generating entities;
    1. Delete lines 390 thru 393 and 231 thru 388
      (These do the actual entity class generation)
    2. Delete lines 86 thru 87 and 64 thru 81
      (These generate EF attrbiutes which are not used for POCO models)

I’ve included this T4 template as seperate download at the end of the post.

Now I can write my own classes and as long as the shape of the classes matches my model EF will just pick them up and use them at runtime. Some quick points on the classes I’m writing;

  • I’m choosing not to expose the key properties on OrderLine to clients, something that would have been harder to do if I was generating classes from a template
  • For my change tracking approach all my classes derive from my custom class StatefulObject which has a State property that gets set to “Unchanged” as it leaves the server, I’ve also included a helper method to convert this state to the equivavlent state used by EF when we play back changes on the server.
public enum State
{
    Added,
    Unchanged,
    Modified
}

[DataContract(IsReference = true)]
public class StatefulObject
{
    [DataMember]
    public State State { get; set; }

    [OnSerializing]
    internal void SetUnchanged(StreamingContext context)
    {
        this.State = State.Unchanged;
    }
} 

public static class StateHelpers
{
    public static EntityState GetEquivelantEntityState(State state)
    {
        switch (state)
        {
            case State.Added:
                return EntityState.Added;
            case State.Modified:
                return EntityState.Modified;
            default:
                return EntityState.Unchanged;
        }
    }
}

Here is what my entity classes look like;

[DataContract(IsReference = true)]
public class Order : StatefulObject
{
    [DataMember]
    public Guid OrderId { get; set; }

    [DataMember]
    public DateTime OrderDate { get; set; }

    [DataMember]
    public List<OrderLine> OrderLines { get; set; }

    [DataMember]
    public Customer Customer { get; set; }

    [DataMember]
    public byte[] ConcurrencyToken { get; set; }
}

[DataContract(IsReference=true)]
public class Customer : StatefulObject
{
    [DataMember]
    public Guid CustomerId { get; set; }

    [DataMember]
    public string FirstName { get; set; }

    [DataMember]
    public string LastName { get; set; }

    [DataMember]
    public byte[] ConcurrencyToken { get; set; }
}

[DataContract(IsReference = true)]
public class OrderLine : StatefulObject
{
    public Guid OrderId { get; set; }
    public Guid ProductId { get; set; }

    [DataMember]
    public int Quantity { get; set; }

    [DataMember]
    public Product Product { get; set; }

    [DataMember]
    public Order Order { get; set; }

    [DataMember]
    public byte[] ConcurrencyToken { get; set; }
}

[DataContract(IsReference = true)]
public class Product : StatefulObject
{
    [DataMember]
    public Guid ProductId { get; set; }

    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public byte[] ConcurrencyToken { get; set; }
}

Change Tracking

Now for change tracking, basically this is the “insert preferred method” section, I’ll just re-iterate if you don’t want to worry about how change tracking is done then this isn’t the article for you… you want to wait for Self Tracking Entities.

I’m going with the following;

  • I’m using a “ConcurrencyToken” property on each entity to avoid the need to store original values (there are APIs you can use to work with original values if you want to do property level change tracking, see ApplyOriginalValues under the “Quick Summary of APIs” section)
  • My entities all derive from a base class that has a state property on it which can be Added, Unchanged or Modified (again this is just how I’m choosing to do things, there are plenty of other approaches). The client is responsible for setting this property to the appropriate value.
  • As already mentioned I’m not sharing any classes between tiers so the client is just going to get the standard proxy classes that get generated for service references.

The Service

I’m just implementing two methods in the service, one to do the actual persistence of and order and another that will fetch the details of an order, this second method is just to help us out with some end to end tests at the end.

[ServiceContract]
public interface IService
{
    [OperationContract]
    Order GetOrder(Guid orderID);

    [OperationContract]
    void SaveOrder(Order order);
}

Ok we finally get to see the N-Tier APIs in action here, the code pretty much speaks for itself, there is just one thing I want to call out;

  • There is “not-so-pretty” bit of code there to deal with the possibility that the Customer that the order belongs to has changed, that block of code will go away once we have “Foreign Key Associations” available (coming after Beta 1). I’ll also add that using this new association type does not mean we have to expose FK properties to clients to get this bennefit.
public class Service : IService
{
    /// <summary>
    /// Persists the supplied order
    /// </summary>
    /// <param name="order">An order object with Customer, OrderLines & OrderLine.Product populated</param>
    public void SaveOrder(Order order)
    {
        //TODO: Validate incoming object graph

        using (SimpleOrderEntities ctx = new SimpleOrderEntities())
        {
            //Add the order and realted objects into the context
            ctx.AddToOrders(order);

            //Get order into the correct state
            ctx.ObjectStateManager.ChangeObjectState(order, StateHelpers.GetEquivelantEntityState(order.State));
            ctx.ObjectStateManager.ChangeObjectState(order.Customer, StateHelpers.GetEquivelantEntityState(order.Customer.State));

            //If order isn't added then we may be changing the customer that it belongs to
            //The need for this block of code will go away once Foreign Key Associations are available
            if (order.State != State.Added)
            {
                //I'm querying for the original customer but you could also keep track of the original value
                var originalCustomer = ctx.Orders.Where(o => o.OrderId == order.OrderId).Select(o => o.Customer).Single();
                if (originalCustomer != order.Customer)
                {
                    ctx.ObjectStateManager.ChangeRelationshipState(order, originalCustomer, o => o.Customer, EntityState.Deleted);
                }
                else
                {
                    ctx.ObjectStateManager.ChangeRelationshipState(order, order.Customer, o => o.Customer, EntityState.Unchanged);
                }
            }

            //Set the state on each OrderLine and associated Product
            foreach (var line in order.OrderLines)
            {
                ctx.ObjectStateManager.ChangeObjectState(line, StateHelpers.GetEquivelantEntityState(line.State));
                ctx.ObjectStateManager.ChangeObjectState(line.Product, StateHelpers.GetEquivelantEntityState(line.State));

                //Since Order and Product define the key of an OrderLine these relationships must be unchanged unless it's a new OrderLine
                if (line.State != State.Added)
                {
                    ctx.ObjectStateManager.ChangeRelationshipState(line, order, l => l.Order, EntityState.Unchanged);
                    ctx.ObjectStateManager.ChangeRelationshipState(line, line.Product, l => l.Product, EntityState.Unchanged);
                }
            }

            //Persist changes to the store
            //TODO: Process Concurrency / Update Failures
            ctx.SaveChanges();
        }
    }

    /// <summary>
    /// Fetches an order
    /// </summary>
    /// <param name="orderID">Key of the order to fetch</param>
    /// <returns>An order object with Customer, OrderLines & OrderLine.Product populated</returns>
    public Order GetOrder(Guid orderID)
    {
        using (SimpleOrderEntities ctx = new SimpleOrderEntities())
        {
            return ctx.Orders
                .Include("OrderLines")
                .Include("OrderLines.Product")
                .Include("Customer")
                .Where(o => o.OrderId == orderID)
                .Single();
        }
    }
}

Does it Work?

Of course it does … but in case you want play with it, the Solution contains a test project with a few examples of the service in action;

[TestClass]
public class EndToEndTests
{
    [TestMethod]
    public void AddNewOrder()
    {
        using (ServiceClient srv = new ServiceClient())
        {
            Order order = BuildNewOrder();
            srv.SaveOrder(order);

            var order_fetch = srv.GetOrder(order.OrderId);
            Assert.AreEqual(order_fetch.OrderId, order.OrderId);
            Assert.IsNotNull(order_fetch.Customer);
            Assert.AreEqual(order_fetch.Customer.CustomerId, order.Customer.CustomerId);
            Assert.IsNotNull(order_fetch.OrderLines);
            Assert.AreEqual(order_fetch.OrderLines.Count, 1);
            Assert.AreEqual(order_fetch.OrderLines[0].Product.ProductId, order.OrderLines[0].Product.ProductId);
        }
    }

    [TestMethod]
    public void ChangeCustomer()
    {
        using (ServiceClient srv = new ServiceClient())
        {
            Order new_order = BuildNewOrder();
            srv.SaveOrder(new_order);

            var order = srv.GetOrder(new_order.OrderId);
            order.Customer = new Customer
            {
                CustomerId = Guid.NewGuid(),
                FirstName = "Joe",
                LastName = "Bloggs",
                State = State.Added
            };
            srv.SaveOrder(order);

            var order_fetch = srv.GetOrder(new_order.OrderId);
            Assert.IsNotNull(order_fetch.Customer);
            Assert.AreEqual(order_fetch.Customer.CustomerId, order.Customer.CustomerId);
            Assert.AreNotEqual(order_fetch.Customer.CustomerId, new_order.Customer.CustomerId);
        }
    }

    [TestMethod]
    public void ChangeScalarProperty()
    {
        using (ServiceClient srv = new ServiceClient())
        {
            Order new_order = BuildNewOrder();
            srv.SaveOrder(new_order);

            var order = srv.GetOrder(new_order.OrderId);
            order.OrderDate = order.OrderDate.AddDays(1);
            order.State = State.Modified;
            srv.SaveOrder(order);

            var order_fetch = srv.GetOrder(new_order.OrderId);
            Assert.AreEqual(order.OrderDate, order_fetch.OrderDate);
        }
    }

    [TestMethod]
    public void AddOrderLine()
    {
        using (ServiceClient srv = new ServiceClient())
        {
           Order new_order = BuildNewOrder();
            srv.SaveOrder(new_order);

            var order = srv.GetOrder(new_order.OrderId);
            order.OrderLines.Add(new OrderLine
            {
                Order = order,
                Quantity = 1,
                State = State.Added,
                Product = new Product
                {
                    ProductId = Guid.NewGuid(),
                    Name = "Windows 7",
                    State = State.Added
                }
            });
            srv.SaveOrder(order);

            var order_fetch = srv.GetOrder(new_order.OrderId);
            Assert.AreEqual(order.OrderLines.Count, 2);
        }
    }

    private static Order BuildNewOrder()
    {
        Order order = new Order
        {
            OrderId = Guid.NewGuid(),
            OrderDate = DateTime.Today,
            OrderLines = new List<OrderLine>(),
            State = State.Added
        };

        order.Customer = new Customer
        {
            CustomerId = Guid.NewGuid(),
            FirstName = "Rowan",
            LastName = "Miller",
            State = State.Added
        };

        order.OrderLines.Add(new OrderLine
        {
            Order = order,
            Quantity = 20,
            State = State.Added
        });

        order.OrderLines[0].Product = new Product
        {
            ProductId = Guid.NewGuid(),
            Name = "VS2010 Beta1",
            State = State.Added
        };
        return order;
    }
}

A Quick Summary of the APIs I used (+ some I didn’t)

ObjectContext.AddObject

This API was around in EF v1 and is by far the easiest way of getting things back into the context, mainly because it is the most relaxed API and doesn’t require unique keys etc. From here it is really easy to shuffle things around into the correct state. The name is a little misleading as it will in fact add an object and also any other entities that are reachable by traversing navigation properties.

//Add the order and realted objects into the context
ctx.AddToOrders(order);

ObjectStateManager.ChangeObjectState

Not much to say here, basically it just moves the entity into the specified state. Depending on the target state it may have an impact on the surrounding relationships (i.e. moving an Object to the Added state will also move any Unchanged relationships to the Added state). Marking an entity as Modified will mark all scalar values as modified.

//Moves order to Modified state and marks all proeprties as modified
ctx.ObjectStateManager.ChangeObjectState(order, EntityState.Modified);

ObjectStateManager.ChangeRelationshipState

This will change the existing relationship between two entities to the specified state or if there isn’t a relationship it will create a new one in the specified state. This is handy because changing a relationship (i.e. moving order to a different customer) requires an Added relationship to the new parent and a deleted relationship to the original parent. If we already have the new relationship created we can paint in the original relationship. I’ll add that this requirement goes away if you swap to using Foreign Key Associations which will be available in the next instalment of the VS2010 release cycle, this makes life a lot easier and I look forward to blogging on that at a later date.

//Creates a deleted relationship between the order and it's former customer
ctx.ObjectStateManager.ChangeRelationshipState(order, originalCustomer, o => o.Customer, EntityState.Deleted);

ObjectStateEntry.ChangeState

This API-behaves the same as ChangeObjectState or ChangeRelationshipState depending on whether the ObjectStateEntry in question is for an object or relationship.

//Moves order to Modified state and marks all proeprties as modified
ctx.ObjectStateManager.GetObjectStateEntry(order).ChangeState(EntityState.Modified);

ApplyCurrentValues

This API existed as ApplyPropertyChanges in EF v1 and basically takes the scalar values from the supplied object and copies them into the object with the same key. Any values that differ from the original values will be marked as modified.

Customer customer_orig = new Customer
{
    CustomerId = Guid.NewGuid(),
    FirstName = "Bob",
    LastName = "Jones"
};

Customer customer_modified = new Customer
{
    CustomerId = customer_orig.CustomerId,
    FirstName = "Robert",
    LastName = "Jones"
};

ctx.AttachTo("CustomerSet", customer_orig);
ctx.ApplyCurrentValues("CustomerSet", customer_modified);

ApplyOriginalValues

The problem with ApplyCurrentValues is that in N-Tier scenarios we usually want to attach the current entity (which has relationships setup with all the other current entities) and then have a way to express what the original values were. So now we have ApplyOriginalValues which work the same as ApplyCurrentValues but updates the original values.

Customer customer_orig = new Customer
{
    CustomerId = Guid.NewGuid(),
    FirstName = "Bob",
    LastName = "Jones"
};

Customer customer_modified = new Customer
{
    CustomerId = customer_orig.CustomerId,
    FirstName = "Robert",
    LastName = "Jones"
};

ctx.AttachTo("CustomerSet", customer_modified);
ctx.ApplyOriginalValues("CustomerSet", customer_orig);

Source Code

As promised here are the downloads;

Advertisements