Thursday, December 9, 2010

Using the EntityFramework POCO repository implementation in WCF – Part 2

The POCO entities that we used in the WCF implementation in the previous post are detached from the object context when passed to the client application. Before trying to perform operations like Save, insert or delete we need to attach these objects to the context and mention the change on the entity so that the Context.SaveChanges method updates the model accordingly. Let’s see an example on how the Customer object with some orders added and which have some changes are updated in the DB when the update method is called on the repository.
public override void Update(Customer entity)
{
    OrderRepository orderRepository = new OrderRepository(this.Context as IUnitOfWork);
    orderRepository.SaveAll(entity.Orders);
    base.Update(entity);
}

The CustomerRepository’s Update method first updates/ adds orders via the order repository. The order repository has a SaveAll method that iterates through all orders and perform the necessary operations on the order.
public class OrderRepository : BaseRepository<Order>, IOrderRepository
{  

    public void SaveAll(IEnumerable<Order> orders)
    {
        orders.ToList().ForEach(order =>
            {
                if (order.Id == Guid.Empty)
                    Add(order);
                else
                    Update(order);
            });           
    }
}

The BaseRepository also has some changes inorder to attach and add the additional information on the attached entity to do the CRUD.
public abstract class BaseRepository : EF4Recipies.Repository.IRepository where T : BaseEntity
{
   
    public virtual void Add(T entity)
    {
        entity.Id = entity.Id == Guid.Empty ? Guid.NewGuid() : entity.Id;
        entity.CreatedDate = entity.ChangedDate = DateTime.Now;
        ValidateAndAddToObjectSet(entity);
    }

    private void ValidateAndAddToObjectSet(T entity)
    {
        entity.Validate();
        Attach(entity);
        Context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Added);
    }

    public virtual void Remove(T entity)
    {
        Attach(entity);
        Context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Deleted);
        Context.DeleteObject(entity);
    }

    public virtual void Update(T entity)
    {
        Attach(entity);
        Context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Modified);
        var entityFromDb = GetById(entity.Id);
        if (entityFromDb != null && StructuralComparisons.StructuralEqualityComparer.Equals(entity.Timestamp, entityFromDb.Timestamp))
        {
            entity.ChangedDate = DateTime.Now;
            entity.Validate();
        }
        else
            throw new InvalidOperationException("Concurrency exception");
    }
}
Test cases:
[TestMethod]
public void UpdateOrders_should_insert_new_orders_created_and_save_the_changes_on_the_existing_ones()
{
    var proxy = new ShoppingCartServiceClient.ShoppingCartServiceClient("WSHttpBinding_IShoppingCartService");
    var customerWithOrders = proxy.GetById(new Guid("1931FEDC-2CCA-437E-A3BD-D5842879530D"));
    if (customerWithOrders != null)
    {
        customerWithOrders.Email = "myNewEmail@mailserver.com";
        customerWithOrders.Orders.Add(
            new Order
            {
                OrderDate = DateTime.Now,
                OrderNumber = string.Format("ORD{0:MMddYYYYHHmmss}", DateTime.Now),
                CustomerId = customerWithOrders.Id,
                Customer = customerWithOrders
            });

        var existingOrder = customerWithOrders.Orders.First(o => o.Id != Guid.Empty);
        existingOrder.OrderNumber = string.Format("ORDCH{0:MMddYYYYHHmmss}", DateTime.Now);
        proxy.UpdateOrders(customerWithOrders);
        var order = proxy.SearchByOrderNumberAndCustomerDetails("ORDCH", customerWithOrders);
        Assert.IsNotNull(order);

    }
    else
        Assert.IsFalse(true, "Failed to load customer with orders");
}

No comments: