views:

104

answers:

1

I've been googling but haven't found any good info on building a good search function in a ASP.NET MVC project.

Off the top of my head, a search feature for searching authors and books would look like this:

char[] delimiterChars = { ' ', ','};
string[] searchterms = SearchBox.Split(delimiterChars);
IQueryable<SearchResult> SR = _db.Books.Where(e => 
    (e.Title.Contains(SearchTerm[0]) || e.Author.Name.Contains(SearchTerm[0]))
    && (e.Title.Contains(SearchTerm[1]) || e.Author.Name.Contains(SearchTerm[1]))
    //&& repeat for as many words were entered in the search box
    )
      .Select(e => new SearchResult
      {
          Title = e.Title,
          Type = "Book",
          Link = "Book/" + e.BookID
      });

Questions

  1. Is there a better way of doing this?
  2. If not, how can I dynamically construct my SQL query to look for as many search terms as were entered?
  3. Can I append search results from a second search to the SR variable?

Other Considerations

In the search above I am searching for results that match the Book or Author. Maybe immediately after I want to do a search just looking for a match in the Author table, and then append those results to the SR variable results from the first query. I don't know if that's the most practical approach, but can it be done?

A: 

My first inclination would be to offload this to something like Lucene.Net. Short of that, you could do this:

char[] delimiterChars = { ' ', ','};
string[] searchterms = SearchBox.Split(delimiterChars);

var temp = _db.Books.AsQueryable();
foreach (string term in searchterms)
{ 
  temp = temp.Where(e => 
    (e.Title.Contains(term) || e.Author.Name.Contains(term));
}

IQueryable<SearchResult> results = temp.Select(e => new SearchResult
{
  Title = e.Title,
  Type = "Book",
  Link = "Book/" + e.BookID
});

Note the foreach that dynamically builds the query.

gWiz
Thanks, but my search results are of type SearchResult, not Book. I created an object/class of SearchResult so I can fit the results of many different objects (Books, Authors, etc) into one search result.Because of this, in your example, I get an error on this line: IQueryable<Book /* ? */> temp = _db.Books;
johnnycakes
I'm sorry I had a typo, it should have read `IEnumerable<Book>`. But that was just a guess as to what the type of _db.Books is (which is why I put the "?" comment in there). Anyway, we don't need the explicit typing of the temp variable. I've changed the declaration to use var. Try compiling the sample again.
gWiz
@gWiz - In the line "temp = temp.Where(...)", aren't you redeclaring temp as the search results of the first searchTerm and then searching that object, in the end giving you nothing? Shouldn't you create some type of List that can have search results added to it?
Baddie
@Baddie The question indicated that the number of search terms is user-determined. The OP's search algorithm filters _db.Books for each search term. The line you refer to in my code achieves this. It continually adds a new where expression node to the expression tree represented by the IQueryable temp; i.e. it filters whatever the "current" set of Books is (temp), saves this filtered set as the new current set, and repeats for all search terms. This yields the results in terms of Books; the next line converts to SearchResults.
gWiz
@ gWiz - Ok, that makes sense, but if the search terms are "stack overflow", wouldn't the first run of the code filter all the _db.Books from everything BUT books that contain "stack" in their title or their author name? When the second term, "overflow" or any other second term, is searched for, wouldn't the search only look for Books that now contain "overflow" in the books that contain "stack"? Is this what the OP intended to happen?
Baddie
@Baddie, yes that was my intention. That way if someone types "salinger catcher rye" we will get the correct result because "salinger" will hit on author AND "catcher" will hit on title AND "rye" will hit on title. If you have any other suggestions as to approach, please share.
johnnycakes
@gWiz, I'm getting an error on temp.Where. "Cannot implicitly convert type 'System.Linq.IQueryable<Sol.Models.Book>' to 'System.Data.Linq.Table<Sol.Models.Book>'" Any ideas?Thanks.
johnnycakes
@johnnycakes - try changing this line "var temp = _db.Books;" to "var temp = _db.Books.AsQueryable();"
Baddie
Agreed, I updated the sample. Thanks Baddie.
gWiz
Btw, I tend to agree that this search algorithm *might* not yield usable results. If a user mis-spells a word for example, they could end up without any results. That's because this is an "All" matching algorithm. Alternatively, an "Any" matching algorithm would find Books with occurrences of any word, and optionally rank them (e.g. by count of matches). In this case, you would have to iteratively build an intermediate list of results, as Baddie pointed out. *But* it all depends on what the user expects.
gWiz
@gWiz - thanks for your help. Can you point me to an example of the "Any" matching algorithm you describe
johnnycakes
gWiz
How can I repeat that for as many words were entered in the search box? Or is that what you mean by "making this dynamic"? Thanks.
johnnycakes
Yeah that's what I mean. You would do what Baddie mentioned. Basically create a variable 'temp' of type List<Book> and for each search term, find all the matching items from `_db.Books` and **add them to 'temp'**. Next, order them by frequency (`temp.GroupBy(b => b.BookID).OrderByDescending(g => g.Count()).Select(g=>g.First());`) and then create your SearchResults as before.
gWiz
Thanks, I think I've got it.Lastly, my other question wasn't answered: Can I append search results from a second search to the SR variable?Thanks.
johnnycakes
Sure. `IEnumerable<Searchresult> allResults = results.Union(otherResults);`
gWiz