Friday, March 26, 2010

It is all about CRUD. (Part 2)

Ok, now we have base generic class that will do standard CRUD and GetQuery for us (see previous post). However real life impersonated as your current client is full of surprises and fancy requirements ;) What if we want to do custom CRUD or extend standard one? For example, consider this classic schema

image

What if we want to execute some custom business logic each time we add new Order and update OrderItem? Solution (as usual) is simple

Code Snippet
[EnableClientAccess]
public class OrderService : CompositeServiceBase<Order, TestEntities>
{
    public override void Insert(Order item)
    {
        base.Insert(item);
        // Put custom logic here for adding Order if required
        // this operation is inside transaction scope - throw exception to rollback changes
        
    }
    
    public void UpdateOrderItem(OrderItem item)
    {
        // Put custom logic here for updating OrderItem if required

    }

yes, just override corresponding CRUD operation for root type (Order) and add method with correct signature (RIA uses name conventions by default) for child entity (OrderItem). You might have noticed that comments saying something about transactions…let me explain what’s going on here – by default all CRUD operations for DomainService executed outside transaction scope (we just attach, insert, delete entities to EF’s context).

However imagine that we need to call stored procedure from our CRUD method as extension for standard operation and this SP will modify something (calculate your salary for example) – we need to make sure that this calculation will be atomic as well as standard CRUD (which is maintained by EF when RIA call ObjectContext.SaveChanges()) That’s why we need this base class

Code Snippet
/// <summary>
/// Provides base repository and transactional support + common entity processing before save
/// </summary>
/// <typeparam name="T">ObjectContext</typeparam>
public abstract class DomainServiceBase<T> : LinqToEntitiesDomainService<T>
    where T : ObjectContext, IObjectContext, new()
{
    public override void Initialize(DomainServiceContext context)
    {
        base.Initialize(context);
        DataContext.Current = ObjectContext;
    }

    public abstract void ProcessEntityBeforeSave(EntityObject entity);

    public override bool Submit(ChangeSet changeSet)
    {
        // do base CRUD inside transaction
        bool result;

        var changes = changeSet.ChangeSetEntries
            .Where(c => c.Operation == DomainOperation.Insert || c.Operation == DomainOperation.Update)
            .Select(c => c.Entity).Cast<EntityObject>();

        foreach (var entity in changes)
        {
            ProcessEntityBeforeSave(entity);
        }

        using (var tx = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
        {
            result = base.Submit(changeSet);
            if (!ChangeSet.HasError)
            {
                tx.Complete();
            }
        }

        return result;
    }

ProcessEntityBeforeSave method can be defined in derived class to implement common entities processing – in our example it can be assigning LastUpdated field to current DateTime

Code Snippet
public override void ProcessEntityBeforeSave(EntityObject entity)
{
    UpdateHelper.UpdateCommonFields(entity);
}

You might wonder what is DataContext.Current? well… wait for next post about Using Repository pattern in RIA ;)

No comments:

Post a Comment