简体   繁体   中英

Filtered tokenised search result with Linq and EF

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

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

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM