Comments

In part 1 of this post I implemented a custom repository method to find all ingredients containing a keyword. The obvious improvement for this query was to extend the search to multiple keywords. As usual the first step was to write a unit test for the new method:

        protected override void LoadTestData()
        {
            CreatePersistedIngredient(1, "Chicken thigh", FoodGroup.Meat);
            CreatePersistedIngredient(2, "Chicken breast", FoodGroup.Meat);
            CreatePersistedIngredient(3, "Red Pepper", FoodGroup.Vegetable);
            CreatePersistedIngredient(4, "Jalapeno Chilli", FoodGroup.Vegetable);
        }

        [Test]
        public void CanFindIngredientsByKeywords()
        {
            List<Ingredient> ingredients = ingredientRepository.FindByKeywords(new string[] { "chilli", "pepper" }).ToList();
            ingredients.ShouldNotBeNull();
            ingredients.Count.ShouldEqual(2);
        }

The test expects 2 ingredients to be returned for the search both the Red pepper and Jalapeno Chilli ingredients should be returned.

Predicate functions and Expression trees

I wanted to keep using the Linq-to-NHibernate library for the data access queries as this ensures that the queries inherit all the benefits of Linq. I also find it more intuitive to use than HQL, for simple queries at least! The core problem was how to write a Linq query that could be extended for an arbitrary number of keywords. In HQL this could be achieved by looping over the keywords and building up the query string. However it is not so easy to iteratively build a lambda function.

My first effort involved chaining together predicate functions. However after passing the chained predicate to NHibernate the rather less than impressive result was nothing for any query! Looking at the SQL query produced showed that an incomplete statement was being passed without any of the filter keywords. I was unable to find any documentation to speak of for Linq-to-NHibernate but the NHibernate user group did prove useful. They suggested that I may have more success building up an expression tree and using that as the predicate.

Dynamic Linq-to-NHibernate Query

After reading up on expression trees I was in a position to code up a method to dynamically build the predicate. While researching the best way to tackle this, the internet again reminded me that there are not many original problems left in this world! This article by Joseph Albahari on dynamically composing expression predicates demonstrated how a helper class could be used to farm out the work of building up the predicate. The following class makes use of the PredicateBuilder class provided in the article:

        public IEnumerable<Ingredient> FindByKeywords(IEnumerable<string> keywords)
        {
            Expression<Func<Ingredient, bool>> predicate = PredicateBuilder.False<Ingredient>();

            foreach (string keyword in keywords)
            {
                string temp = keyword;
                predicate = predicate.Or(i => i.Name.Contains(temp));
            }
            return Session.Linq<Ingredient>().Where(predicate);
        }
  • The expression predicate is initialised to false
  • The predicate is chained together using the Or method of predicate builder class
  • The final predicate will return true for any Ingredient that has a Name containing any of the keywords.

The FindByKeywords method passes the unit test at the start of the post. Implementing this method took a while, mostly due to the time taken to read up on the predicate functions and expression trees. However I think it was time worth spending as these language features are here to stay and will most likely increase in use over time. Also I now have a handy way of writing efficient Linq queries with multiple filter parameters :)

Comments

In this post I will be looking at the best way to re-factor a data access query which is used on the add ingredients page. Initially the query was written quickly with no consideration given to the time it would take to execute. It is not currently a problem as the database is very small but in future the page could be very slow to load.

    ingredientsModel.MatchingIngredients = IngredientRepository.GetAll()
        .Where(i => i.Name.ToLower().Contains(ingredientsModel.SearchIngredient.ToLower())).ToList();

This is a poorly optimised query as the generic Sharp Architecture repository GetAll() method call retrieves the entire data set from the database. It is then filtered in memory to get the actual matching ingredients. For larger datasets, it would be much quicker if the data is filtered before it is retrieved from the database. The query could be rewritten to use the FindAll(IDictionary propertyValuePairs) method like this:

    ingredientsModel.MatchingIngredients = IngredientRepository
        .FindAll(new Dictionary { { "Name", ingredientsModel.SearchIngredient.ToLower()} });

This does solve the optimisation issue as the resulting database SQL query uses a Where clause to pre-filter the data. However, the query is limited to retrieving exact matches to the search string. It is unlikely that users will input the exact ingredient they are searching for, so it would be better if the query retrieved all ingredients that contain the search term.

Unit Testing the Custom Repository

In order to do this I will use a custom repository and add my own query method. Much credit is due to the Sharp Architecture example projects, which provide a good basis for the techniques I ve used here. First, I’ll write a unit test for the new repository method. The test class is shown below and each section is explained after the class:

    [TestFixture]
    public class IngredientRepositoryTests : RepositoryTestsBase
    {
        protected override void LoadTestData()
        {
            CreatePersistedIngredient(1, "Chicken thigh", FoodGroup.Meat);
            CreatePersistedIngredient(2, "Chicken breast", FoodGroup.Meat);
            CreatePersistedIngredient(3, "Red Pepper", FoodGroup.Vegetable);
            CreatePersistedIngredient(4, "Jalapeno Chilli", FoodGroup.Vegetable);
        }

        private IIngredientRepository ingredientRepository = new IngredientRepository();

        private Ingredient CreatePersistedIngredient(int ingredientId, string ingredientName, FoodGroup foodGroup)
        {
            Ingredient ingredient = new Ingredient(ingredientName, foodGroup);
            ingredientRepository.SaveOrUpdate(ingredient);
            FlushSessionAndEvict(ingredient);
            return ingredient;
        }

        [Test]
        public void CanFindIngredientsByName()
        {
            List<Ingredient> ingredients = ingredientRepository.FindByName("chicken").ToList();
            ingredients.ShouldNotBeNull();
            ingredients.Count.ShouldEqual(2);
        }
    }
  • The unit test inherits from the RepositoryTestsBase (namespace SharpArch.Testing.NUnit.NHibernate) which enables test data to be easily loaded into the in-memory database by overriding the the LoadTestData method.
  • The ingredientRepository property is the custom repository data access class
  • The CreatePersistedIngredient method is used in the LoadTestData method to save a single ingredient entity to the in-memory database. Note the call to FlushSessionAndEvict is used to persist the saved entity to the database
  • The CanFindIngredientsByName method is our test method which runs the repository FindByName method 
  • The test method asserts that the ingredients list is populated with 2 Ingredient entities

Implementing the Custom Repository

Now we are ready to implement the custom repository for the accessing ingredient data. Again, the class is shown below with explanations following it:

    public class IngredientRepository : Repository<Ingredient>, IIngredientRepository
    {        
        public IEnumerable<Ingredient> FindByName(string ingredientName)
        {
            return Session.Linq<Ingredient>().Where(a => a.Name.Contains(ingredientName));
        }
    }
  • The custom repository inherits from the generic sharp architecture Repository class, ensuring that all the standard access methods are available
  • It implements the IIngredientRepository interface. This enables IoC injection of the repository into the controllers.
  • The FindByName method implements a Linq to NHibernate query using the Where method. This will be transformed into a SQL query using a Where clause to prefilter the data before it is retrieved.

The custom repository method passes the unit test and returns the expected data. The original query is now optimised and will perform much better against larger data sets. I ve improved the efficiency of the data access, however I d also like to improve the functionality. So in the next post I will be looking at extending the ingredient repository with a new dynamic query. This will enable searching by an arbitrary number of keywords.

Comments

I’ve been working on re-factoring the add recipe feature to include a draft recipe domain object. However the recipe controller has been steadily expanding, along with the number of unit tests to support it. This was causing me issues as it was getting difficult to scan through the files to find the methods I’m interested in. So I’ve introduced some custom routing and split up the recipe controller.


Original recipe controller signature
This was the recipe controller signature before introducing the customer routing (for brevity in this post I’ve extracted the controller signature to an interface, the interface is not actually used in the project):


    interface IRecipesController
    {
        ActionResult Index(string title);

        ActionResult EditIngredients(string recipeTitle);
        ActionResult EditIngredients(RecipeIngredientsModel ingredientsModel);

        ActionResult EditMethod(string recipeTitle);
        ActionResult AddRecipeIngredient(RecipeIngredientsModel ingredientsModel);
        ActionResult SearchIngredients(RecipeIngredientsModel ingredientsModel);
        ActionResult EditMethod(RecipeMethodModel methodModel);

        ActionResult EditSummary(string recipeTitle);
        ActionResult EditSummary(RecipeSummaryModel summaryModel);   
    }

So that’s 9 public methods, along with some private methods, taking the total up to around 15. I thought that was too many for the class to be easily manageable.


Custom Routing Implementation


In Sharp Architecture projects the custom routing instructions need to be added in the RouteRegistrar.cs file, located in the Web.Controllers assembly. Handily, the Sharp architecture example project had an example similar to what I was trying to achieve, so I just needed to tweak the area and route URL’s to make my customer routing:


    routes.CreateArea("Recipes", "RecipeBook.Web.Controllers.Recipes",
        routes.MapRoute(null, "Recipes/{controller}/{action}", new { action = "Index" }),
        routes.MapRoute(null, "Recipes/{controller}/{action}/{id}")
    );

Coding Tweaks


An interesting result of this routing change was the tweaks that needed to be made to the code:

  • The action links in Views must now use the Html.ActionLinkForAreas() method instead of Html.ActionLink()
  • RedirectToAction calls in controllers must include the controller name
  • Tests for Controller Actions, which redirect to another controller, should make use of the ToController(controllerName) MvcContrib TestHelper extension method.

Revised controller signatures


After implementing the custom routing the controllers and tests could be broken up into 4 separate files with the following method signatures:


    interface IRecipesController    
    {
        ActionResult Index(string title);
    }
    interface ISummaryController    
    {
        ActionResult Edit(string recipeTitle);              
        ActionResult Edit(RecipeSummaryModel summaryModel); //Post        
    }
    interface IMethodController    
    {
        ActionResult Edit(string recipeTitle);
        ActionResult Edit(RecipeMethodModel methodModel);   //Post
    }
    interface IRecipeIngredientsController
    {
        ActionResult Edit(string recipeTitle);
        ActionResult AddRecipeIngredient(RecipeIngredientsModel ingredientsModel);  //Post
        ActionResult Edit(RecipeIngredientsModel ingredientsModel);                 //Post    
        ActionResult SearchIngredients(RecipeIngredientsModel ingredientsModel);    //Post
    }
Comments

The add recipes feature has currently been implemented in a way that does not allow for dirty domain objects to be persisted (saved to the database). The recipe object constructor must be passed all the essential object properties to prevent a partial object from being persisted, as shown below:


        public Recipe(string title, IList recipeIngredients, string cookingMethod) : this()
        {
            //TODO - Add the recipe creator (ie. user) to the domain signature
            Check.Require(title != null && title.Trim() != string.Empty, "title must be provided.");
            Check.Require(recipeIngredients != null, "recipeIngredients must be provided.");
            Check.Require(cookingMethod != null && cookingMethod.Trim() != string.Empty, "method must be provided.");
            Title = title;
            Ingredients = recipeIngredients;
            CookingMethod = cookingMethod;        
        }

However this means that the state of the recipe object must be maintained across the 3 separate add recipe views. I’ve had to use both hidden fields on the views and the TempDataDictionary to maintain this state. It has become clear to me that there may be a better way to implement this if I use a RecipeDraft domain object in addition to the Recipe object. However making this change will take a while so I need to weigh up whether I think it is worth while.


Advantages

  • Users can add draft recipes and return later to complete them
  • More readable controller methods (RecipeIngredientDtos is a nasty object name!)
  • No need to maintain temporary state across views (using hidden fields and the TempDataDictionary)
  • Simplifed View Models

Disadvantages

  • Time to refactor the code and unit tests
  • More records in the database (potentially higher cost)
  • More I/O operations on the database (slower website page loads)

On balance I think the advantages are outweigh the disadvantages, so I will be refactoring the code to use a RecipeDraft object. This will be the constructor of the RecipeDraft object:


        public RecipeDraft(string title) : this()
        {
            //TODO - Add the recipe creator (ie. user) to the domain signature
            Check.Require(title != null && title.Trim() != string.Empty, "title must be provided.");
            Title = title;      
        }
Comments

I have used an SVN repository for the project, hosted on unfuddle, enabling versioned source control on all files in the project. I can use the trusty TortoiseSVN Windows application to send and receive files to the remote repository directly from Windows Explorer. After each work session I commit the changes to source control. Apart from the obvious advantages of maintaining backups, if at any stage I realise that I have made a mistake, then the changes can be rolled back to the previous commit.
The source control recently came in useful when I needed to upgrade the version of Sharp Architecture used on the project. I won t go into detail about the reasons for me upgrading, suffice to say most of the technology stack in Sharp Architecture had benefitted from version updates and I was limiting myself by using a pre version 1.0 release.
These are the steps I took to upgrade the Sharp Architecture version of the project:


  1. Use TortoiseSVN to Branch the code in source control from /trunk to /branches/recipebookV1

  2. Rename the working folder (ie. the local folder in Windows Explorer) to recipebookV1 and switch it to the branch.

  3. Update the Sharp Architecture project to fetch v1.0 from source control and copy the new project template into the Visual Studio templates folder.

  4. Generate a new Visual Studio project, named recipebook, from the v1.0 Sharp Architecture templates.

  5. Use Beyond Compare to copy all the files that I have manually added / changed in the recipebookV1 project to the new recipebook project

  6. Add the files to the recipebook project in Visual Studio, cross fingers, compile and run!

  7. Use the TortoiseSVN repo browser to delete all files from the Trunk yes, I really deleted them all!

  8. Import the new recipebook folder back into the trunk

After completing the upgrade I realised that there may be an easier way to upgrade, without the scary and somewhat bad practice of deleting all the old files from the trunk, only to upload most of them again. I will list them below so I can try them out later:


  1. Use TortoiseSVN to Branch the code in source control from /trunk to /branches/recipebookV1

  2. Rename the working folder (ie. the local folder in Windows Explorer) to recipebookN. This enables a new visual studio project of the same name to be created, with namespaces which are the same as the working project.

  3. Update the Sharp Architecture project to fetch v1.0 from source control and copy the new project template into the Visual Studio templates folder.

  4. Generate a new Visual Studio project named recipebook from the V1.0 Sharp Architecture templates.

  5. Use Beyond Compare to copy all updated Sharp Architecture files from the new recipebook project to the working recipebookn folder

  6. Add any extra files to the working recipebook project in Visual Studio, cross fingers, compile and run!

  7. Commit the changes to the trunk, remove the new recipebook project and rename the existing recipebook project folder to recipebook.

Copyright © 2016 - Hook Technologies Ltd - Powered by Octopress