Introduction
In this tip, I would like to present how to implement Unit of Work pattern together with simple Repository for MS Dynamics CRM 2015.
Background
Repository as a part of Data Access Layer creates separation between business layer and database, providing CRUD (Create
, Retrieve
, Update
and Delete
) operations. Unit of Work provides facility to persist changes made to a model in a single operation. The common way to implement atomic operations is transaction mechanism. Microsoft Dynamics CRM facilitates transaction since the latest version through ExecuteTransactionRequest
. Dynamics CRM message works in all-or-nothing fashion. When executed, it tries to persist all the objects that are part of it, if it fails all the changes are rolled back.
Repository
The following snippet shows basic structure of Repository. To keep code loosely coupled and testable, dependencies used by the class are injected through the constructor. That enables us to easily replace it with test double or other implementation.
public class ContactRepository : IRepository
{
private readonly TestOrganizationContext _context;
private readonly ICrmUnitOfWork _crmUnitOfWork;
public ContactRepository(ICrmUnitOfWork crmUnitOfWork)
{
if (crmUnitOfWork == null)
throw new ArgumentNullException("crmUnitOfWork");
_crmUnitOfWork = crmUnitOfWork;
_context = new TestOrganizationContext(_crmUnitOfWork.OrganisationService);
}
...
}
As you noticed OrganisationService
, which represents connection to Dynamics CRM is passed as a Unit of Work property. This construct is a way to ensure single connection to CRM system, which is the most time consuming operation. It might be helpful in the scenario when you have several repositories and you want to make clear that only a single connection to the system should be made. Otherwise OrganisationService
could be injected through the constructor like Unit of Work object itself.
Repository methods register model changes to the Unit of Work instance. Unit of Work is responsible for persistence of changes. Repository itself does not persist any changes in database.
public void Add(string firstname, string lastname, string email)
{
var contact = new Contact
{
FirstName = firstname,
LastName = lastname,
EMailAddress1 = email
};
_crmUnitOfWork.Add(contact);
}
public void Update(Contact contact)
{
_crmUnitOfWork.Update(contact);
}
public void Delete(Guid contactId)
{
_crmUnitOfWork.Delete(Contact.EntityLogicalName, contactId);
}
Unit of Work
To initialize Unit of Work, we have to provide IOrganisationService
object that represents connection to Dynamics CRM. The second parameter represent batch size. Dynamics CRM batch size limit for ExecuteTransactionRequest
request is 1000 records. Batch size parameter provides a way to save more than 1000 records during Commit
action and set how many records we want to persist in one operation. It gives us an opportunity to adjust number to personal preferences and tune it for the best performance in the system.
public class CrmUnitOfWork : ICrmUnitOfWork
{
private readonly IOrganizationService _service;
private readonly int _batchSize;
public IOrganizationService OrganisationService
{
get { return _service; }
}
private List<CreateRequest> Added { get; set; }
private List<UpdateRequest> Modified { get; set; }
private List<DeleteRequest> Deleted { get; set; }
public CrmUnitOfWork(IOrganizationService service, int batchSize = 100)
{
if (service == null)
throw new ArgumentNullException("service");
_service = service;
_batchSize = batchSize;
Added = new List<CreateRequest>();
Modified = new List<UpdateRequest>();
Deleted = new List<DeleteRequest>();
}
...
}
Whenever Unit of Work instance is called by repository, it creates one of Dynamics CRM messages and queues it on a list of objects that should be persisted.
public void Add(Entity entity)
{
Added.Add(
new CreateRequest
{
Target = entity
});
}
Once we are ready to save changes to CRM system, we call Commit()
method. The first method gathers all different requests into OrganizationRequestCollection
. Then it loops through collection and executes transaction every time it reaches batch size limit.
public void Commit()
{
var requests = new OrganizationRequestCollection();
var transactionRequest = new ExecuteTransactionRequest
{
Requests = new OrganizationRequestCollection()
};
Added.ForEach(requests.Add);
Modified.ForEach(requests.Add);
Deleted.ForEach(requests.Add);
Added.Clear();
Modified.Clear();
Deleted.Clear();
try
{
for (int i = 0; i < requests.Count; i++)
{
if ((i + 1) % _batchSize != 0)
{
transactionRequest.Requests.Add(requests[i]);
}
else
{
transactionRequest.Requests.Add(requests[i]);
var response = _service.Execute(transactionRequest);
transactionRequest = new ExecuteTransactionRequest
{
Requests = new OrganizationRequestCollection()
};
}
if ((i == requests.Count - 1) && transactionRequest.Requests.Count > 0)
{
var response = _service.Execute(transactionRequest);
}
}
}
catch (FaultException<OrganizationServiceFault> fault)
{
...
}
}
To perform transaction in Dynamics CRM, we have to execute ExecuteTransactionRequest
. This message is available only from Dynamics CRM 2015, previous versions of Microsoft product do not enable any easy way to perform multiple requests within a single transaction. Instead, you can utilize ExecuteMultipleRequest
, which semantically works in very similar way but instead of saving changes in atomic operation, it saves objects one after another. That will enable you to implement Unit of Work pattern in a similar fashion but you will have to provide custom rollback mechanism to keep data consistency.
Below is a simple example of usage.
var crmConnection = new CrmConnection(
ConfigurationManager.AppSettings["OrganisationServiceUrl"],
ConfigurationManager.AppSettings["Username"],
ConfigurationManager.AppSettings["Password"]);
var crmUnitOfWork = new CrmUnitOfWork(crmConnection.ServiceProxy);
var repository = new ContactRepository(crmUnitOfWork);
repository.Add("test1", "contact", "email1@company.com");
repository.Add("test2", "contact", "email2@company.com");
crmUnitOfWork.Commit();
Summary
After reading this tip, you should be able to implement your own data access layer for Dynamics CRM project. Repository and Unit of Work pattern can be implemented in more than one way and the final design will depend on the project requirements, but as a rule of thumb, try not to hide class dependencies as it will limit your abilities to extend Data Access Layer with new implementations (e.g. multiple repositories) and it will make it harder to test it.
You can find the entire project with the latest updates and example of usage on my github repository.