Up Development guide
DevForce 2010 Resource Center » Cocktail overview » Development guide » Unit Of Work Pattern And Persistence Ignorance

Unit Of Work Pattern And Persistence Ignorance

Last modified on August 30, 2012 00:06

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 obtaining a correctly configured 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.


Introduction

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. Cocktail 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.

Everything is asynchronous!

As we dive into the details of the Unit Of Work Pattern and look at the interfaces and classes contained in Cocktail, 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.

A simple example

Let's first look at a concrete example before we go any further. Using the out-of-the-box functionality in Cocktail, the following coroutine updates a customer's company name using a unit of work.

C#
public IEnumerable<INotifyCompleted> UpdateCompanyName()
{
    var provider = new EntityManagerProvider<EntityManager>();
    var uow = new UnitOfWork<Customer>(provider);

   // Retrieve customer
   var id = new Guid("ed99343d-b368-4007-90a2-48ccf2699d44");
    OperationResult<Customer> operation;
   yield return operation = uow.Entities.WithIdAsync(id);

   // Update company name
   operation.Result.CompanyName = "Test";

   // Commit changes
   yield return 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. Cocktail ships with generic implementations of all the components, which as you can see can be used as-is for simple scenarios like this one.

Persistence Ignorance

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.

The What and How

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 Cocktail provides.

UnitOfWork

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 Cocktail 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 Cocktail 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>
   /// <param name="onSuccess"> Callback to be called if the commit was successful. </param>
   /// <param name="onFail"> Callback to be called if the commit failed. </param>
   /// <returns> Asynchronous operation result. </returns>
   OperationResult<SaveResult> CommitAsync(Action<SaveResult> onSuccess = null, Action<Exception> onFail = null);

   /// <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;
}

Repository

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.

Cocktail 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>
   /// <param name="onSuccess"> Callback to be called when the entity retrieval was successful. </param>
   /// <param name="onFail"> Callback to be called when the entity retrieval failed. </param>
   /// <returns> Asynchronous operation result. </returns>
   OperationResult<T> WithIdAsync(object keyValue, Action<T> onSuccess = null, Action<Exception> onFail = null);

   /// <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="orderBy"> Optional sorting function to sort the returned list of entities. </param>
   /// <param name="includeProperties"> Optional related entities to eager fetch together with the returned
   ///      list of entities. Use comma to separate multiple properties. </param>
   /// <param name="onSuccess"> Optional callback to be called when the entity retrieval was successful. </param>
   /// <param name="onFail"> Optional callback to be called when the entity retrieval failed. </param>
   /// <returns> Asynchronous operation result. </returns>
   OperationResult<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate = null,
                                              Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
                                             string includeProperties = null,
                                              Action<IEnumerable<T>> onSuccess = null,
                                              Action<Exception> onFail = 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 IEnumerable<INotifyCompleted> RetrieveAllCustomersInLondon()
{
    var provider = new EntityManagerProvider<EntityManager>();
    var uow = new UnitOfWork<Customer>(provider);

    OperationResult<IEnumerable<Customer>> operation;
   yield return operation = uow.Entities.FindAsync(x => x.City == "London", q => q.OrderBy(x => x.CompanyName));

    var customers = operation.Result;
}

Paging

Cocktail 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="includeProperties"> Optional related entities to eager fetch together with the returned
   ///      list of entities. Use comma to separate multiple properties. </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,
                   string includeProperties = 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>
   /// <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);
}

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 void GoToFirstPage()
    {
        _pager.FirstPageAsync()
            .ContinueWith(op => UpdateFromPage(op.Result));
    }

   public void GoToNextPage()
    {
        _pager.NextPageAsync()
            .ContinueWith(op => UpdateFromPage(op.Result));
    }

   private void UpdateFromPage(Page<Customer> page)
    {
       if (page.PageWasFound)
            Customers = new BindableCollection<Customer>(page.Results);
    }
}

Factory

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.

Cocktail 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>
   /// <param name="onSuccess">Callback to be called if the entity creation was successful.</param>
   /// <param name="onFail">Callback to be called if the entity creation failed.</param>
   /// <returns>Asynchronous operation result.</returns>
   OperationResult<T> CreateAsync(Action<T> onSuccess = null, Action<Exception> onFail = null);
}

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.

Using an entity factory method

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.

Subclassing Factory<T>

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 OperationResult<StaffingResource> CreateAsync(Action<StaffingResource> onSuccess = null,
                                                                    Action<Exception> onFail = null)
    {
       return Coroutine.Start(CreateAsyncCore, op => op.OnComplete(onSuccess, onFail))
            .AsOperationResult<StaffingResource>();
    }

   protected IEnumerable<INotifyCompleted> CreateAsyncCore()
    {
        var staffingResource = StaffingResource.Create();
        EntityManager.AddEntity(staffingResource);

        OperationResult<IEnumerable<AddressType>> op1;
       yield return op1 = _addressTypes.FindAsync(t => t.Default);
        var addressType = op1.Result.First();
        var address = staffingResource.AddAddress(addressType);
        staffingResource.PrimaryAddress = address;

        OperationResult<IEnumerable<PhoneNumberType>> op2;
       yield return op2 = _phoneNumberTypes.FindAsync(t => t.Default);
        var phoneType = op2.Result.First();
        var phoneNumber = staffingResource.AddPhoneNumber(phoneType);
        staffingResource.PrimaryPhoneNumber = phoneNumber;

       yield return Coroutine.Return(staffingResource);
    }
}

Domain Service

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 Cocktail 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
{
    OperationResult<IEnumerable<StaffingResourceListItem>> Simple(
       string text, Action<IEnumerable<StaffingResourceListItem>> onSuccess = null, Action<Exception> onFail = null);
}

public class StaffingResourceSearchService : IStaffingResourceSearchService
{
   private readonly IRepository<StaffingResource> _repository;

   public StaffingResourceSearchService(IRepository<StaffingResource> repository)
    {
        _repository = repository;
    }

   #region IStaffingResourceSearchService Members

   public OperationResult<IEnumerable<StaffingResourceListItem>> Simple(
       string text, Action<IEnumerable<StaffingResourceListItem>> onSuccess = null, Action<Exception> onFail = null)
    {
        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
                                    }),
            filter, q => q.OrderBy(i => i.LastName), onSuccess, onFail);
    }

   #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
{
    OperationResult<bool> CheckIfDuplicateAsync(
        Customer customer, Action<bool> onSuccess = null, Action<Exception> onFail = null);
}

public class ValidationService : IValidationService
{
   private readonly IRepository<Customer> _customers;

   public ValidationService(IRepository<Customer> customers)
    {
        _customers = customers;
    }

   public OperationResult<bool> CheckIfDuplicateAsync(Customer customer, Action<bool> onSuccess = null,
                                                        Action<Exception> onFail = null)
    {
       return Coroutine.Start(() => CheckIfDuplicateCore(customer), op => op.OnComplete(onSuccess, onFail))
            .AsOperationResult<bool>();
    }

   private IEnumerable<INotifyCompleted> CheckIfDuplicateCore(Customer customer)
    {
        OperationResult<IEnumerable<Guid>> operation;
       yield return operation = _customers.FindAsync(
            q => q.Select(x => x.CustomerID), x => x.CompanyName == customer.CompanyName);

       yield return Coroutine.Return(operation.Result.Any());
    }
}

A simple UnitOfWork

We have seen this already in a couple of examples above. Cocktail 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)
    {
    }
}
Note how the constructor makes sure that MEF always injects a new instance of an EntityManagerProvider to satisfy the requirement that a UnitOfWork should always be sandboxed.

Creating a custom UnitOfWork

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; }
}
Tags:
Created by DevForce on August 02, 2012 11:48

This wiki is licensed under a Creative Commons 2.0 license. XWiki Enterprise 3.2 - Documentation. Copyright © 2015 IdeaBlade