Tuesday, December 7, 2010

Using the EntityFramework POCO repository implementation in WCF – Part 1

Entity Framework 4.0 allows creation and usage of POCO entities for implementing persistence ignorant entity types from an Entity Data Model. We can use a WCF service and expose operation contracts to the clients so that they can perform operations like insert, save, get etc on these entities. In this post I’ll use the sample created in the post implementation of the Repository using EF 4.0 POCO objects and expose them via a WCF service.
We need to make some changes to the object model and context to expose it using WCF. The object model which I have created has multiple references within itself when being serialized. For e.g. the customer can have multiple orders and the order has a reference back to the customer. When the WCF's data contract serializer attempts to serialize this object, it results in a stack overflow exception due to the cyclic relationship between the parent-child relation. .NET 3.5 SP1 includes a IsReference parameter in the DataContract attribute which will automatically retain the cyclic object relationships for the objects. We can use this attribute on the base entity in our sample to mention cyclic references.
[DataContract(IsReference = true)]
public abstract class BaseEntity
{
    [DataMember]
    public Guid Id { get; set; }

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

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

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

    public abstract void Validate();
}

Next we need to turn off change tracking proxies created for our entities. These would normally be created because we have marked the entities properties as virtual. These proxies cannot be serialized and if they were generated the navigation properties on the POCO objects will not serialize and your response comes back empty resulting in the “The underlying connection was closed: The connection was closed unexpectedly” exception. For turning off change tracking proxies we need to add the below given code to the BaseRepository constructor
public BaseRepository(IUnitOfWork unitOfwork)
{
    if (unitOfwork == default(ShoppingCartContext)) throw new ArgumentNullException("UnitOfWork cannot be null");
    Context = unitOfwork as ShoppingCartContext;
    Context.ContextOptions.LazyLoadingEnabled = false;
    Context.ContextOptions.ProxyCreationEnabled = false;
}

Since Lazy load is disabled, to include the child entities in the model, we need to explicitly specify the include statements for each objects. For e.g  the CustomerRepository GetAll method is changed to
public override IEnumerable<Customer> All()
{
    return Context.Customers
        .Include("Orders")
        .Include("Orders.OrderProducts")
        .Include("Orders.OrderProducts.Product");
}
This will include the Orders, OrderProducts for the orders and Product for each orderProduct in the Orders for the customer.
Now we can try to use the updated model in our service.
[ServiceContract]
public interface IShoppingCartService
{
    [OperationContract]
    Customer GetById(Guid id);
}

public class ShoppingCartService : IShoppingCartService
{
    public ShoppingCartService()
    {
        unitOfWork = UnitOfWork.Default;
    }

    public Customer GetById(Guid id)
    {
        var customerRepository = new CustomerRepository(unitOfWork);
        return customerRepository.GetById(id);
    }

    IUnitOfWork unitOfWork;
}

Test cases
[TestMethod]
public void GetAllOrdersByCustomer_should_retrive_all_orders_placed_by_a_customer()
{
    var proxy = new ShoppingCartServiceClient.ShoppingCartServiceClient("WSHttpBinding_IShoppingCartService");
    var customer = proxy.GetById(new Guid("1931FEDC-2CCA-437E-A3BD-D5842879530D"));
    Assert.IsNotNull(customer);
}

Next we’ll add new methods to the service to perform more operations on the context to add, remove and update properties on the entities.

1 comment:

Anonymous said...

Thanks for the post. This solved the underlying connection was closed error. But now there's a different problem.

I have user and role that has cyclic reference. After adding your two points, when Find(..).include(Role), I end up having Users with roles, but for each role, it also list all the users in the result set who have those roles.

Any idea how this happens? Thanks