使用 Linq 和 EF 过滤标记化搜索结果

发布于 2024-11-28 22:25:16 字数 5079 浏览 0 评论 0原文

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.例如

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.

- - - - - - - - - - - - - - - - - - - -编辑 - - - - - ----------------------------------

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

流绪微梦 2024-12-05 22:25:16

只是猜测,问题会通过引入 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;

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文