使用 Linq 和 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.例如
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
只是猜测,问题会通过引入
DefaultIfEmpty()
自行解决吗?Just a guess, would the problem correct itself with the introduction of
DefaultIfEmpty()
?