1

I'm looking for a way to create a filtering system based upon tokenised query strings to return a list of farms.

The filtering mechanism would hopefully be flexible enough for me to supply the tokens in any order to return results.

The rules for search would be like this:

state:WA crop:Banana

would give me a filtered list of all farms in WA with the crop banana.

crop:Banana state:WA

should return the same result.

city:Albany crop:Banana

would give me a filtered list of all farms in Albany with the crop banana.

Each of the values supplied could be wrapped in quotation marks to allow space separated values to be grouped. e.g

city:"Mount barker" crop:Banana

would give me a filtered list of all farms in Mount Barker with the crop banana.

Furthermore any non tokenised queries would just look within a farms Details property to return the list of farms again with quotation marks combining multiple word queries.

---------------------------------------EDIT--------------------------------------------

My current search system using predicates is coded as follows. it's long (sorry) and is my first attempt though I'm hoping this could be refactored by some kind soul.

Many thanks in advance:

    public ActionResult Search(string query, int? page)
    {
        IQueryable<Farm> farms = this.ReadOnlySession.All<Farm>();

        if (!String.IsNullOrWhiteSpace(query))
        {
            // http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder
            var predicate = PredicateBuilder.True<Farm>();

            // We want to replace the spaces in quoted values here so we can split by space later.
            // http://stackoverflow.com/questions/2148587/regex-quoted-string-with-escaped-quotes-in-c
            Regex quoted = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*""");

            foreach (var match in quoted.Matches(query))
            {
                query = query.Replace(match.ToString(), match.ToString().Replace(' ', '-'));
            }

            // Tidy up the query to remove "".
            string[] splitQuery = HttpUtility.UrlDecode(query).Replace("\"", "").Split(' ');
            Dictionary<string, string> tokenDictionary = new Dictionary<string, string>();

            // Loop through our string[] and create a dictionary. Guids used to allow multiple keys
            // of the same value.
            Parallel.ForEach(splitQuery, subQuery =>
            {
                string[] tempArray = subQuery.Split(':');

                if (tempArray.Length == 2)
                {
                    tokenDictionary.Add(String.Format("{0}:{1}", tempArray[0], Guid.NewGuid()), tempArray[1]);
                }
                else
                {
                    tokenDictionary.Add(String.Format("description:{0}", Guid.NewGuid()), subQuery);
                }
            });

            // Loop through the dictionary and create our predicate.
            foreach (KeyValuePair<string, string> item in tokenDictionary)
            {
                string value = item.Value.Replace('-', ' ');
                string key = item.Key.Split(':')[0].ToUpperInvariant();

                switch (key)
                {
                    case "CROP":
                        value = Utilities.CreateSlug(value, OzFarmGuideConfig.RemoveDiacritics);
                        predicate = predicate.And(x => x.Crops.Any(y => value.Equals(y.Slug, StringComparison.OrdinalIgnoreCase)));
                        break;
                    case "STATE":
                        predicate = predicate.And(x => value.Equals(x.City.State.Name, StringComparison.OrdinalIgnoreCase));
                        break;
                    case "CITY":
                        value = Utilities.CreateSlug(value, OzFarmGuideConfig.RemoveDiacritics);
                        predicate = predicate.And(x => value.Equals(x.City.Slug, StringComparison.OrdinalIgnoreCase));
                        break;
                    default:
                        predicate = predicate.And(x => !String.IsNullOrWhiteSpace(x.Details) &&  x.Details.Contains(value));
                        break;
                }
            }

            farms = farms.Where(predicate).OrderByDescending(x => x.Rating)
                                          .ThenByDescending(x => x.RatingVotes);

            PagedList<Farm> pagedFarms = new PagedList<Farm>(farms, page.HasValue ? page.Value - 1 : 0, 5);


            return View(pagedFarms);
        }
        else
        {
            PagedList<Farm> pagedFarms = null;
            return View(pagedFarms);
        }
    }
James South
  • 10,147
  • 4
  • 59
  • 115

1 Answers1

0

Just a guess, would the problem correct itself with the introduction of DefaultIfEmpty()?

default:
    // This is not working at the mo. Getting a null exception when we try
    // to initialise PagedList.
    predicate = predicate.And(x => x.Details.DefaultIfEmpty().Contains(value));
    break;
ckittel
  • 6,478
  • 3
  • 41
  • 71
  • I'd actually fixed the problem with with my !StringIsNullOrWhitespace call but forgot to update the code comment. I'll check out your suggestion though as it looks cleaner. I guess I'm really looking to see if I'm doing anything stupid now.... Thanks! – James South Aug 11 '11 at 08:17
  • Oh, is `Details` a string? If so, my suggestion wouldn't be correct. I thought it was a collection (list) of some kind. – ckittel Aug 11 '11 at 11:39
  • Yeah it is.... Sorry if that wasn't clear. I'm having trouble just now working in predicate.Or statements. for some reason when I use them all farms are returned. – James South Aug 11 '11 at 12:44
  • @James okay, I'll delete my answer (when I can) as this wouldn't help your problem at all. Sorry for the noise. – ckittel Aug 11 '11 at 13:38
  • Not at all..... Any interest is good! I'm actually going to post some code once I've tidied it up since it looks like I've nailed it for the most part. – James South Aug 11 '11 at 13:40