Tuesday, 26 June 2012

7. Silverlight + RIA Services – Transactions

If your domain service is derive from LinqToEntitiesDomainService<TContext> using Entity Framework, the transaction is handled automatically.

However if you are making the Doamin Service by inheriting DomainService class then your ll have to make the following arrangement for handling Transaction.

Note: When you call SubmitChanges() from the client on the DomainContext, it calls Submit on the server. So transactions can be incorporated by overriding Submit(ChangeSet) method with the base implementation and wrapping it in a transaction scope.

public class CustomDomainService : DomainService
    {
        public override bool Submit(ChangeSet changeSet)
        {
            bool result;
            using (var tx = new TransactionScope(TransactionScopeOption.Required,new TransactionOptions     { IsolationLevel = IsolationLevel.ReadCommitted }))
            {
                result = base.Submit(changeSet);
                if (!this.ChangeSet.HasError)
                {
                    tx.Complete();
                 }
            }
            return result;
        }
    }
If you browse the definition of DomainService then you will find the above said Submit here as below -

Let's get into more details -  What happens when SubmitChanges() is called at the Client side -

The first thing that will happen is the creation of the DomainService, this is done through a Domain Service Factory. The Domain Service Factory will make a call to the DomainService Initialize method. 


After the Initialization the Submit method of the DomainService will be executed. This method will execute the following DomainService methods in the following order:
1) AuthorizationChangeSet
2) ValidateChangeSet
3) ExecuteChangeSet
4) PersistChangeSet
5) ResolveChangeSet

Note: Next focus on virtual method which you can overwrite. 
You can override the Submit method and do something before the Submit will take place, and also do things after the Submit is executed, for example do some pre- and post conditions like logging etc.
public override bool Submit(ChangeSet changeSet)
{
        //do something here pre submit
        var submitResult = base.Submit(changeSet);
        //do something here post submit    
        return submitResult;
}
Note: If you want to log exceptions that will take place during a Submit, it’s recommended that you override the OnError method and log the exception within that method. The base.Submit will make a call to the OnError if there was any error during the process of the Submit method, so it will not take place after the Submit method. There is a try and catch block around all the above mentioned methods within the base.Submit method so if anyone of them will fail the Submit method will make a call to the OnError an then throw the exception up the chain.
Also Note: The PersistChangeSet methods of the LinqToEntitiesDomainService can also make a call to the OnError method if a concurrency conflict occurs during the persistent of the entities.

Next let's see what's there in the ChangeSet object which is used by every methods explained above -

The ChangeSet object The ChangeSet object is like a Unit of Work (things that should be done in a specific order). The ChangeSet will contains a list of Domain Operations that should take place and also the entity which is part of that operation, for example if you do the following operation on the client-side: 

updateCustomer.Name = "Jane Doe";
var newCusotmerc = new Customer() { CustomerID = 2, Name = "Fred Doe" };
domainContext.Customers.Add(newCustomer);
domainContext.Customers.Remove(removeCustomer);
domainContext.SubmitChanges();
The ChangeSet’s ChangeSetEntries will have three ChangeSetEntry:

Operation = Insert, Entity = newCustomer
Operation = Update, Entity = updateCustomer
Operation = Delete, Entity = removeCustomer

The Operation property of the ChangeSetEntry has the name of the operation that should take place. A operation method is the method which you add to the DomainService to Insert, Update or Delete an entity. The method use a naming convention where the prefix of the method name is the name of the operation, the method can only take an Entity as argument, here is an example of three operation methods added to the DomainService: 
public void InsertCustomer(Customer customer)
        
public void UpdateCustomer(Customer customer)
       
public void DeleteCustomer(Customer customer)
Now to the different xxxxxChangeSet methods. 
AuthorizeChangeSet
The AuthorizeChangeSet method is the first method that will be executed when the Submit method is called. This method will validate and see if the current user are allowed to make a call to the DomainService operations. The AuthorizeChangeSet method will iterate through the ChageSet and see if any of the Operations in ChangeSet or the “custom” methods has any of the the AuthorizeAttribute specified (ReuqiredAuthenticationAttribute or RequiredRoleAttrtibute). The AuthorizeAttribute is used to prevent non-authorized user to make a call to an operation method. If the current user isn’t allowed to execute any of the operations in the ChangeSet an exception will be thrown and the OnError method of the DomainService will be called, and the execution of the Submit will ended. Here is an example where the AuthroizateAttribute is used: 
[RequiresAuthentication(), RequiresRole("Admin")]
public void AddCustomer(CustomerDto customer)

If the current user isn’t authenticated or the authenticated user don’t belong to the role “Admin”,  the AuthorizeChangeSet will fail and an exception is thrown.

ValidateChangeSet
The next method that will be executed if the authorize validation passed during the Submit is the ValdiateChangeSet. This method will perform the server-side validation when the validation annotation is used on entities. If the validation fails on a entity in the ChangeSet the OnError method will be called and the Submit method will break. The following is an example about how to use the validation annotation on a Entity Framework or Linq to SQL generated class: 
[MetadataTypeAttribute(typeof(Customer.CustomerMetadata))]
public partial class Customer
{
    internal sealed class CustomerMetadata
    {
         private CustomerMetadata()
         {
         }

         [Required]
         [RegularExpression("[A-Z][A-Za-z0-9]*")]
         [StringLength(32)]
         public string CompanyName;

         ...
        }
    }
}
If you use a DTO/”Presentation Model” no meta data types are needed, instead you can just add the validation attributes to the property of your DTO/”Presentation Model”: 
public class Customer
{
      [Required]
      [RegularExpression("[A-Z][A-Za-z0-9]*")]
      [StringLength(32)]
      public string CompanyName { get; set;}

         ...
}
Note: The validation will take place on the client-side, so the WCF RIA Services validation annotation will do both a client-side validation and server-side. It’s the ValidateChangeSet method that will do the validation on the server-side.
ExecuteChangeSet
The ExecuteChangeSet will take place after the validation is passed. This method will make two internal execution, it will first iterate through the ChangeSet and execute the CUD (Create, Update and Delete) Operations, and then the “custom” methods will be executed if there are any in the ChangeSet. If the ChangeSet has the following ChangeSetEntries: 
Operation = Insert, Entity = newCustomer
Operation = Update, Entity = updateCustomer
Operation = Delete, Entity = removeCustomer 
and the following operation methods in the DomainService: 
public void InsertCustomer(Customer customer)
        
public void UpdateCustomer(Customer customer)
       
public void DeleteCustomer(Customer customer)
The first method to be executed during the ExecuteChangeSet will be the InsertCustomer then the UpdateCustomer and last the DeleteCustomer. When the ExecuteChangeSet method is completed the PersitChangeSet method will be executed.

PersistChangeSet
The PersistChangeSet method will make sure to save the inserted, updated and deleted entities to a data source. If the LintToEntityDomainService is used the PersistChangeSet will make a call to the ObjectContext’s SaveChanges method. If a normal DomainService is used (when you use DTO/”Presentation Model”), the PresistChangeSet will do nothing if you don’t implement it. If you for example uses nHibernate you can call the nHibernate’s Session’s Save method in the PersistChangeSet, if the other operation methods just add entities to or remove entities from the nHibernate’s session. If you simply use the Repository pattern, Business Logic Components or Data Access components, you don’t need to use the PersistChangeSet, instead the operations method will work against the components instead (of course it’s up to you and based on the way you want to persist the model).
The last method that will be executed by the Submit method is the ResolveChangeSet, it will only be executed if the ChangeSet’s Entities got any conflicts during the process of the Submit method. 
ResolveChangeSet
The ResolveChangeSet will try to resolve any conflicts appeared during the execution of the Submit method. If the conflicts are solved the method will return true otherwise false. If LinqToEntityDomainService or the LinqToSqlEntityDomainService is used, the ResolveChangeSet will make a call to the ObjectContext’s SaveChanges or the DataContext’s SubmitChanges after it succeeded to resolve the entities.
Summary
Basically there is no reason to have a open transaction during the execution of some of the methods. The only methods that may need the Transaction is the ExecuteChangeSet or the PersistChangeSet and maybe the ResolveChangeSet method, but still it's based on how you have implemented your own DomainSerivce. If you use the LinqToEntityDomainService the SaveChanges uses an implicit transaction or enlist in any ambient transaction and does all the necessary work in that transaction. The LinqToEntityDomainService will try to SaveChanges after it tries to resolve some of the conflict during the ResolveChangeSet method.
The reason why you may want to being the transaction within the Submit method is to make sure the ResolveChangeSet will be using the same transaction as the PersistChangeSet and also the other methods, it's depends on the Business Layer and Data Access Layer you are using. Another solution to avoid having an transaction open before the PersistChangeSet is called, is by starting a transaction within the PersistChangeSet and Commit or rollback the exception within the Submit method, then the ResolveChangeSet will also reuse the same transaction if you need to do that: 
private TransactionScope _trans = null;

protected override bool PersistChangeSet(ChangeSet changeSet)
{
     if (_trans == null )
         _trans = new TransactionScope();
     return base.PersistChangeSet(changeSet);
}
        
public override bool Submit(ChangeSet changeSet)
{
      bool submitResult = false;
      try
      {
           var submitResult = base.Submit(changeSet);
           if (trans != null)
               trans.Complete();
       }
       catch
       {
           if (trans != null)
           {
               trans.Dispose();
               trans = null;
            }
       }

       return submitResult;
}
EDIT: In most case you don't need to reuse the same Transaction used within the PersistChangeSet and the ResolveChangeSet, it depends on the Business Logic layer and Data Access Layer you are using. If you use a Transaction for the PersistChangeSet it will do a rollback if there was any conflicts or Commit if there wasn't. In the case where the transaction will Commit there will be no conflicts so the ResolveChangeSet will not be called. If there was a conflict, the transaction will do a rollback and the ResolveChangeSet will then be executed. The ResolveChangeSet can then use its own Transaction when it try to resolve the conflicts and submit changes while using the LinqToSqlDomainService or LinqToEntiesDomainService.

If you are using DTO/”Presentation Model” and your operation methods are working directly against your Business layer from the DomainService, you can start the transaction within the ExecuteChangeSet method, but it will also depends on your DAL.

No comments:

Post a Comment