In a typical application a user can perform many tasks and workflows. These tasks and workflows contain business logic and need to access data. So far we have looked at Views and ViewModels as the means to build the user interaction. We have also looked at the EntityManagerProvider as the means to abstract the application from the EntityManager, but we haven't looked at how the ViewModels should interact with the EntityManager obtained through an EntityManagerProvider. In this chapter we look at the Unit Of Work Pattern as an approach to manage such tasks and workflows, and hide the persistence details from the ViewModel.
The Unit Of Work Pattern, as the name quite accurately describes, is a design pattern to encapsulate anything from a simple task to a complex workflow initiate by the user. Such a task or workflow from the application's perspective could be short or long lived, may involve retrieving data, modifying data, creating new data, perform business logic and check business rules and save or roll back any of the changes.
The Unit Of Work Pattern strictly follows the single responsibility principal, so therefore it is made up of several components that together provide the complete functionality. Punch ships a number of interfaces and a base implementation of the pattern that can be used as-is or be the foundation for one's custom implementation.
As we dive into the details of the Unit Of Work Pattern and look at the interfaces and classes contained in Punch, you'll most likely notice that none of the asynchronous methods have a synchronous equivalent in WPF. This is not a mistake, but rather by design. Long running synchronous methods are a recipe for a performance disaster. To learn more about why, read Marcel Good's Best Practices for the real World blog post.
Let's first look at a concrete example before we go any further. Using the out-of-the-box functionality in Punch, the following method updates a customer's company name using a unit of work.
C# | public async Task UpdateCompanyName() { var provider = new EntityManagerProvider<EntityManager>(); var uow = new UnitOfWork<Customer>(provider); // Retrieve customer var id = new Guid("ed99343d-b368-4007-90a2-48ccf2699d44"); var customer = await uow.Entities.WithIdAsync(id); // Update company name customer.CompanyName = "Test"; // Commit changes await uow.CommitAsync(); } |
The above simple example demonstrates two components of the Unit Of Work Pattern in action. The first component is the UnitOfWork itself and the second component is the Customer Repository that the UnitOfWork makes available via its Entities property. Punch ships with generic implementations of all the components, which as you can see can be used as-is for simple scenarios like this one.
In case you haven't noticed, you just witnessed Persistence Ignorance. Looking at the above example, you'll notice that there are no persistence details other than the EntityManager type in the creation of the EntityManagerProvider, but that is Inversion of Control at play right there. In an actual application we would use MEF to inject the EntityManagerProvider dependency into the UnitOfWork.
What's relevant is that there is no LINQ query to retrieve the customer entity and no mention of where the customer entity comes from. For all you know it could come from memory, or it could be loaded from fake sample data or it could in fact come from a real database. All these details are encapsulated in the Customer Repository. The caller is completely ignorant to how the customer with the given ID is being retrieved. Same goes for persisting the changes back. The caller simply calls CommitAsync and what that means is completely left up to the unit of work.
So now that we have seen a concrete use of the Unit Of Work Pattern, let's look at what makes up the pattern and what Punch provides.
The core component of the Unit Of Work Pattern is the UnitOfWork itself. As mentioned earlier, the UnitOfWork represents the task or workflow being performed. Its responsibility is to own and track all the data changes made throughout the task and commit or roll them back as a whole. The unit of work acts as the transaction boundary.
The Punch implementation accomplishes this by encapsulating an EntityManager, which tracks all the entities and related changes and either saves or rejects the changes at the end. Since each UnitOfWork represents a specific instance of a task or workflow it should only track and own the data related to the current task or workflow. Therefore, it is imperative that each UnitOfWork is created with its own EntityManager. This is often referred to as sandboxing. A UnitOfWork can always be shared between ViewModels if different parts of the UI work with the same data, but the UnitOfWork itself should be a sandbox.
The implementation in Punch consists of the IUnitOfWork interface and the abstract UnitOfWork class, providing the default implementation for the core functionality.
The following quickly illustrates the functionality provided through IUnitOfWork.
C# | public interface IUnitOfWork : IHideObjectMembers { /// <summary> /// Resets the UnitOfWork to its initial state. /// </summary> void Clear(); /// <summary> /// Returns true if the unit of work contains pending changes. /// </summary> bool HasChanges(); /// <summary> /// Returns true if the provided entity is attached to the current UnitOfWork's EntityManager. /// </summary> /// <param name="entity">Entity to check if attached to current UnitOfWork.</param> bool HasEntity(object entity); /// <summary> /// Commits all pending changes to the underlying data source. /// </summary> Task<SaveResult> CommitAsync(); /// <summary> /// Rolls back all pending changes. /// </summary> void Rollback(); /// <summary> /// Fired whenever an entity associated with the current unit of work has changed in any significant manner. /// </summary> event EventHandler<EntityChangedEventArgs> EntityChanged; } |
The second component of the Unit Of Work Pattern is the Repository. Its responsibility is to provide access to existing entities. Each repository provides access to only one type of entity. This makes it possible to highly optimize the fetching strategy for the given entity type. For example a Repository for a static type such as Color could be optimized such that it loads all colors on the first request and then serves subsequent requests directly from the cache instead of making additional expensive roundtrips to the server. Or as an alternative approach, the application could maintain a global cache of frequently used entities at which point the repository would import the necessary entities from the global cache into it's own local cache first, before serving requests directly from it's cache.
Punch ships with a generic Repository implementation consisting of the IRepository<T> interface and the Repository<T> default implementation. Similar to the UnitOfWork, it encapsulates an EntityManager, but in this case the EntityManager is shared with the UnitOfWork. It is the job of the UnitOfWork to initialize each repository with the shared EntityManager and expose the repository through a property on the UnitOfWork as demonstrated in the above example. We must remember that the UnitOfWork tracks all entities and related changes. It can only do that if all entities retrieved by a Repository are kept in the same cache.
IRepository<T> defines three groups of methods. Each group has several overloads to address specific requests such as explicitly retrieving entities from either the cache, the underlying data source or to leave it up to the repository to determine the best strategy. The first group of methods controls the retrieval of a single entity by its primary key. The second group of methods allows for the retrieval of an arbitrary list of entities, including filtering with a predicate, sorting the list and shaping the result in order to return partial entities. The third group of methods marks an entity or list of entities for deletion from the underlying data source.
The following illustrates a few of those methods.
C# | public interface IRepository<T> : IHideObjectMembers { /// <summary> /// Retrieves the entity matching the provided key with the repository's default query strategy. /// </summary> /// <param name="keyValue"> The single primary key value. </param> /// <returns> The retrieved entity. </returns> /// <exception cref="EntityNotFoundException">A single entity matching the provided key was not found.</exception> Task<T> WithIdAsync(object keyValue); /// <summary> /// Retrieves one or more entities matching the provided expression with the repository's default query strategy. /// </summary> /// <param name="predicate"> Optional predicate to filter the returned list of entities </param> /// <param name="cancellationToken"> A token that allows for the operation to be cancelled. </param> /// <param name="orderBy"> Optional sorting function to sort the returned list of entities. </param> /// <param name="fetchOptions">Optional delegate to specify additional fetching options.</param> /// <returns> The list of retrieved entities. </returns> Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, Action<IFetchOptions<T>> fetchOptions = null); /// <summary> /// Marks the specified entity as to be deleted. /// </summary> /// <param name="entity"> Entity to be deleted. </param> void Delete(T entity); ... } |
We've already seen a sample that illustrates retrieving an entity by it's primary key. The following example illustrates how to retrieve all Customers in London and sort them by CompanyName.
C# | public async Task RetrieveAllCustomersInLondon() { var provider = new EntityManagerProvider<EntityManager>(); var uow = new UnitOfWork<Customer>(provider); customers = await uow.Entities.FindAsync(x => x.City == "London", q => q.OrderBy(x => x.CompanyName)); } |
Punch ships with an alternate generic Repository implementation that supports paging, consisting of IPagerRepository<T> and PagerRepository<T>. The following illustrates the additional functionality provided through IPagerRepository<T>.
C# | public interface IPagerRepository<T> : IRepository<T> { /// <summary> /// Returns a pager which allows entities to be paged. /// </summary> /// <param name="sortSelector"> Required sorting criteria. </param> /// <param name="pageSize"> The desired page size. </param> /// <param name="predicate"> Optional predicate to filter the paged entities. </param> /// <param name="fetchOptions">Optional delegate to specify additional fetching options.</param> /// <returns> <see cref="IPager{T}" /> which allows the entities to be paged. </returns> IPager<T> Pager(ISortSelector sortSelector, int pageSize, Expression<Func<T, bool>> predicate = null, Action<IFetchOptions<T>> fetchOptions = null); /// <summary> /// Returns a pager which allows shaped entities to be paged. /// </summary> /// <param name="selector"> The selector used to shape the entities. </param> /// <param name="pageSize"> The desired page size. </param> /// <param name="sortSelector"> Required sorting criteria. </param> /// <param name="predicate"> Optional predicate to filter the paged entities. </param> /// <param name="fetchOptions">Optional delegate to specify additional fetching options.</param> /// <typeparam name="TResult"> The shape of the result. </typeparam> /// <returns> <see cref="IPager{T}" /> which allows the shaped entities to be paged. </returns> IPager<TResult> Pager<TResult>(Func<IQueryable<T>, IQueryable<TResult>> selector, int pageSize, ISortSelector sortSelector, Expression<Func<T, bool>> predicate = null, Action<IFetchOptions<TResult>> fetchOptions = null); } |
An example of a quick-and-dirty ViewModel, which pages through a list of customers is shown below.
C# | public class PagingViewModel : Screen { private readonly CustomUnitOfWorkWithPaging _unitOfWork; private BindableCollection<Customer> _customers; private IPager<Customer> _pager; public PagingViewModel() { // Don't do this in real life! Use dependency injection var provider = new EntityManagerProvider<EntityManager>(); _unitOfWork = new CustomUnitOfWorkWithPaging(provider); } public BindableCollection<Customer> Customers { get { return _customers; } private set { _customers = value; NotifyOfPropertyChange(() => Customers); } } protected override void OnInitialize() { base.OnInitialize(); _pager = _unitOfWork.Customers.Pager(new SortSelector("CompanyName"), 25); GoToFirstPage(); } public async void GoToFirstPage() { var page = await _pager.FirstPageAsync() UpdateFromPage(page); } public async void GoToNextPage() { var page = await_pager.NextPageAsync() UpdateFromPage(page); } private void UpdateFromPage(Page<Customer> page) { if (page.PageWasFound) Customers = new BindableCollection<Customer>(page.Results); } } |
The next component of the Unit Of Work Pattern is the Factory. It's responsibility is to create entire new entities and/or entity graphs. Like the Repository, each Factory encapsulates an EntityManager shared with the UnitOfWork and each Factory is responsible for creating entities of one specific type. The last part is not entirely true if the Factory creates an entire entity graph. In that case each Factory creates an entity graph for one specific root type. We'll show what that means in an example shortly. A Factory may use Repositories to fetch additional data required in order to create the new entity and/or entity graph.
Punch provides a generic implementation consisting of the IFactory<T> interface and the Factory<T> default implementation. For custom factories, consider alternative interfaces, which could allow passing parameters to the create method or methods instead of using IFactory<T>.
The following illustrates the default IFactory<T> interface.
C# | public interface IFactory<T> : IHideObjectMembers { /// <summary> /// Creates a new entity instance of type T. /// </summary> /// <returns>The newly created entity attached to the underlying EntityManager.</returns> Task<T> CreateAsync(); } |
The Factory is the most likely component to be customized first. The process of creating an entity or entity graph is generally very specific to one's application. Each entity may need to be initialized in a very specific way. The default Factory<T> provides several ways to take control of the entity creation process.
The default Factory<T> implementation first looks for a factory method on the entity type. A factory method is a public parameterless static method whose result type is the same as the entity type. The name of the factory method can be any valid .NET identifier. The factory method approach works for cases where the act of creating an entity or entity graph is synchronous, requires no parameters and doesn't require fetching additional data.
The following example illustrates a common factory method that initializes the entity with a proper ID.
C# | public class StaffingResource { internal StaffingResource() { } public static StaffingResource Create() { return new StaffingResource { Id = CombGuid.NewGuid() }; } ... } |
If no suitable factory method is found or the Factory finds more than one suitable method, it looks for a public parameterless constructor and if found will create the entity using the constructor. If neither a factory method nor a parameterless constructor is found, the creation will fail.
In case the process of creating an entity or entity graph is more involved or requires fetching of additional data, then subclassing Factory<T> and customizing the creation logic is more suitable.
The following example demonstrates how to subclass Factory<T> and customize the creation logic.
C# | public class StaffingResourceFactory : Factory<StaffingResource> { private readonly IRepository<AddressType> _addressTypes; private readonly IRepository<PhoneNumberType> _phoneNumberTypes; public StaffingResourceFactory(IEntityManagerProvider<TempHireEntities> entityManagerProvider, IRepository<AddressType> addressTypes, IRepository<PhoneNumberType> phoneNumberTypes) : base(entityManagerProvider) { _addressTypes = addressTypes; _phoneNumberTypes = phoneNumberTypes; } public override async Task<StaffingResource> CreateAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var staffingResource = StaffingResource.Create(); EntityManager.AddEntity(staffingResource); var addressType = (await _addressTypes.FindAsync(t => t.Default, cancellationToken)).First(); var address = staffingResource.AddAddress(addressType); staffingResource.PrimaryAddress = address; cancellationToken.ThrowIfCancellationRequested(); var phoneType = (await _phoneNumberTypes.FindAsync(t => t.Default, cancellationToken)).First(); var phoneNumber = staffingResource.AddPhoneNumber(phoneType); staffingResource.PrimaryPhoneNumber = phoneNumber; return staffingResource; } } |
The fourth and last component of the Unit Of Work Pattern is the Domain Service. Its responsibility is to encapsulate custom logic. A Domain Service is a custom class that provides things like stateless business logic, business rules, custom validation that involves fetching data from the database, complex data queries that may involve relating data from multiple repositories etc.
There are no special interfaces or classes provided by Punch to implement a Domain Service, as every service is completely specific to one's application. A Domain Service may use Repositories and Factories if the logic involves fetching or creating entities.
The following example demonstrates a search service.
C# | public interface IStaffingResourceSearchService : IHideObjectMembers { Task<IEnumerable<StaffingResourceListItem>> Simple(string text); Task<IEnumerable<StaffingResourceListItem>> Simple(string text, CancellationToken cancellationToken); } public class StaffingResourceSearchService : IStaffingResourceSearchService { private readonly IRepository<StaffingResource> _repository; public StaffingResourceSearchService(IRepository<StaffingResource> repository) { _repository = repository; } #region IStaffingResourceSearchService Members public Task<IEnumerable<StaffingResourceListItem>> Simple(string text) { return Simple(text, CancellationToken.None); } public Task<IEnumerable<StaffingResourceListItem>> Simple(string text, CancellationToken cancellationToken) { Expression<Func<StaffingResource, bool>> filter = null; if (!string.IsNullOrWhiteSpace(text)) filter = x => x.FirstName.Contains(text) || x.MiddleName.Contains(text) || x.LastName.Contains(text) || x.Addresses.FirstOrDefault(a => a.Primary).Address1.Contains(text) || x.Addresses.FirstOrDefault(a => a.Primary).Address2.Contains(text) || x.Addresses.FirstOrDefault(a => a.Primary).City.Contains(text) || x.Addresses.FirstOrDefault(a => a.Primary).Zipcode.Contains(text) || x.Addresses.FirstOrDefault(a => a.Primary).State.Name.Contains(text) || x.Addresses.FirstOrDefault(a => a.Primary).State.ShortName.Contains(text) || x.PhoneNumbers.FirstOrDefault(p => p.Primary).AreaCode.Contains(text) || x.PhoneNumbers.FirstOrDefault(p => p.Primary).Number.Contains(text); return _repository.FindAsync( q => q.Select(x => new StaffingResourceListItem { Id = x.Id, FirstName = x.FirstName, MiddleName = x.MiddleName, LastName = x.LastName, Address1 = x.Addresses.FirstOrDefault(a => a.Primary).Address1, Address2 = x.Addresses.FirstOrDefault(a => a.Primary).Address2, City = x.Addresses.FirstOrDefault(a => a.Primary).City, State = x.Addresses.FirstOrDefault(a => a.Primary).State.ShortName, Zipcode = x.Addresses.FirstOrDefault(a => a.Primary).Zipcode, AreaCode = x.PhoneNumbers.FirstOrDefault(p => p.Primary).AreaCode, Number = x.PhoneNumbers.FirstOrDefault(p => p.Primary).Number }), cancellationToken, filter, q => q.OrderBy(i => i.LastName)); } #endregion } |
The following is another example, demonstrating a validation service that can be used by a ViewModel to check if a customer with the same company name already exists in the database before allowing the user to save a new Customer entity.
C# | public interface IValidationService { Task<bool> CheckIfDuplicateAsync(Customer customer); } public class ValidationService : IValidationService { private readonly IRepository<Customer> _customers; public ValidationService(IRepository<Customer> customers) { _customers = customers; } public async Task<bool> CheckIfDuplicateAsync(Customer customer) { var matches = await _customers.FindAsync( q => q.Select(x => x.CustomerID), x => x.CompanyName == customer.CompanyName); return matches.Any(); } } |
We have seen this already in a couple of examples above. Punch ships with a generic one entity type UnitOfWork consisting of IUnitOfWork<T> and UnitOfWork<T>. The implementation of UnitOfWork<T> automatically provides an IFactory<T> and an IRepository<T>.
The following illustrates the functionally provided by the simple UnitOfWork.
C# | public interface IUnitOfWork<T> : IUnitOfWork { /// <summary> /// The factory to create new entity instances. /// </summary> IFactory<T> Factory { get; } /// <summary> /// The repository to retrieve entities. /// </summary> IRepository<T> Entities { get; } } |
This simple UnitOfWork can be used as-is as demonstrated in the above examples or you can create specific types that can be injected into ViewModels via MEF as follows.
C# | [Export(typeof(IUnitOfWork<Customer>)), PartCreationPolicy(CreationPolicy.NonShared)] public class CustomerUnitOfWork : UnitOfWork<Customer> { [ImportingConstructor] public CustomerUnitOfWork( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] IEntityManagerProvider<NorthwindIBEntities> entityManagerProvider) : base(entityManagerProvider) { } } |
Eventually though, tasks and workflows become more complex and require business logic and/or multiple Factories and Repositories, so we'll want to create our own custom UnitOfWork tailored to our use case.
The following example takes the CustomerUnitOfWork from above and enriches it with the validation service from earlier.
C# | public interface ICustomerUnitOfWork : IUnitOfWork<Customer> { IValidationService Validation { get; } } [Export(typeof(ICustomerUnitOfWork)), PartCreationPolicy(CreationPolicy.NonShared)] public class CustomerUnitOfWork : UnitOfWork<Customer>, ICustomerUnitOfWork { [ImportingConstructor] public CustomerUnitOfWork( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] IEntityManagerProvider<NorthwindIBEntities> entityManagerProvider) : base(entityManagerProvider) { Validation = new ValidationService(Entities); } public IValidationService Validation { get; private set; } } |
And finally an example of a complete custom UnitOfWork with a Factory, multiple Repositories and a Service.
C# | public interface IResourceMgtUnitOfWork : IUnitOfWork { // Factories IFactory<StaffingResource> StaffingResourceFactory { get; } // Repositories IRepository<AddressType> AddressTypes { get; } IRepository<State> States { get; } IRepository<PhoneNumberType> PhoneNumberTypes { get; } IRepository<RateType> RateTypes { get; } IRepository<StaffingResource> StaffingResources { get; } // Services IStaffingResourceSearchService Search { get; } } [Export(typeof(IResourceMgtUnitOfWork)), PartCreationPolicy(CreationPolicy.NonShared)] public class ResourceMgtUnitOfWork : UnitOfWork, IResourceMgtUnitOfWork { [ImportingConstructor] public ResourceMgtUnitOfWork( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] IEntityManagerProvider<TempHireEntities> entityManagerProvider) : base(entityManagerProvider) { AddressTypes = new Repository<AddressType>(entityManagerProvider); States = new Repository<State>(entityManagerProvider); PhoneNumberTypes = new Repository<PhoneNumberType>(entityManagerProvider); RateTypes = new Repository<RateType>(entityManagerProvider); StaffingResourceFactory = new StaffingResourceFactory(entityManagerProvider, AddressTypes, PhoneNumberTypes); StaffingResources = new Repository<StaffingResource>(entityManagerProvider); Search = new StaffingResourceSearchService(StaffingResources); } public IFactory<StaffingResource> StaffingResourceFactory { get; private set; } public IRepository<AddressType> AddressTypes { get; private set; } public IRepository<State> States { get; private set; } public IRepository<PhoneNumberType> PhoneNumberTypes { get; private set; } public IRepository<RateType> RateTypes { get; private set; } public IRepository<StaffingResource> StaffingResources { get; private set; } public IStaffingResourceSearchService Search { get; private set; } } |