Feature 3 – searching employees

The last feature that we are going to look at is an ability to search employees in the system. I have seen largest number of variations in the implementation of search features across different systems. The variations are in the way it works, the way results are displayed to end user, and also the complexity of the actual search. Let's begin with the most simple search.

Note

The purpose of this section is not to show you how to implement a search functionality. If you have gone through Chapter 6, Let's Retrieve Some Data from the Database, then you know that implementing search feature comes down to building some queries using ISession and utilizing pagination features offered by ISession. The intent of this section is to show you some limitations of repository pattern. So we will run through the simplest implementation without beating around the bush and discuss issues with repository pattern in the end.

We will implement a feature to search employees by their name. Let's say the search function does not specify first name or last name. Whatever end user enters is checked against employees' first name and last name both. The only restriction that we will apply to keep this simple is that the end user should not enter multiple words. Or in other words, no spaces are allowed. For instance, if you enter John, then search function will return employees having first name or last name as John. Let's move on to the implementation then.

First thing we need is a controller action which would take the input from the end user. This controller action would then pass this input to the next layer. Let's keep this consistent and add a domain service class as the next layer. Let's say we add the following domain service class:

public class EmployeeSearchService
{
  public IEnumerable<Employee> Search(string name)
  {
    //Query the database and return results
  }
}

So we have got a Search function that takes a string parameter for name to match against the first and last names of employees present in database. SearchEmployeeService needs a capability to run that query against database and return the results. Let's add the capability on IRepository<T> and make SearchEmployeeService take dependency on IRepository<T>. Following code listing depicts this:

public class EmployeeSearchService
{
  private readonly IRepository<Employee> repository;

  public EmployeeSearchService(IRepository<Employee> repository)
  {
    this.repository = repository;
  }


  public IEnumerable<Employee> Search(string name)
  {
    return repository.FindAll(name);
  }
}

public interface IRepository<T> where T : EntityBase<T>
{
  void Save(T entity);
  void Update(int id, Employee employee);
  T GetById(int id);
  IEnumerable<Employee> FindAll(string name);
}

We have added a FindAll method on IRepository<T> that takes name as a parameter and returns list of the employee instance found having matching first name or last name. Let's turn our attention now to implementing the FindAll method. If we use LINQ then it is not very difficult. Following is the simplest implementation of this method:

public IEnumerable<Employee> FindAll(string name)
{
  return session.Query<Employee>()
                .Where(e => e.Firstname == name || e.Lastname == name)
                .ToList();
}

This seems to get the work done but there are some issues with the previous approach.

  • The FindAll method on IRepository is specific to the Employee class. This makes generic Repository<T> unusable by other entities. A solution to this is to implement an Employee specific repository but then we would soon add dozen more repository classes that we would need to maintain.
  • Right now we have got two methods on IRepository. One returns an entity by its identifier and second returns list of employees with matching name. As your application grows, these two methods will not be enough. You might need to retrieve employees by their employee number, country/city of residence, email address, and so on. Imagine how would your repository classes look with loads of such methods stacked on to them.
  • We have not yet addressed other concerns around search which are pagination and sorting. If we add pagination and sorting in the preceding query, then the FindAll method becomes too specific to search functionality and cannot be used in other scenarios where we might not need pagination or sorting, for example, to get the count of employees having a particular first name. A solution to this problem could be to not place a call to terminal method ToList within FindAll and return an IQueryable<T> instead of an IEnumerable<T> so that the EmployeeSearchService can then add pagination and sorting as it sees fit. But development community is divided on this approach. A large number of seasoned developers consider returning an IQueryable<T> from repository as a leaky abstraction. I will not go into that debate or pros and cons of either approach as I believe that there is a better solution in the form of a different querying pattern to this problem.

I can go on listing few more issues with our implementation, but let me take a moment and come back to the point I am trying to convey here. There is nothing wrong with implementation of search query and there is no way you can avoid having multiple different ways of querying entities, but stacking everything on to a repository is not wise. Call it a limitation of repository pattern but lot of developers have learned through their experience that building complex queries using repositories mostly results in code that is not testable or maintainable. Next chapter is dedicated to this aspect and solutions available so I will not waste any more space here discussing the issue at length.

That brings us to the end of this chapter. There was no new NHibernate feature that we learned in this chapter, baring contextual session obviously. But we tried to put our NHibernate knowledge into perspective and use it in real-life application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.253.223