Simple search in Sitecore - sitecore

Using Sitecore 8.2 with MVC.
I'm trying to implement the search functionality in a MVC view. (with a textbox and submit button)
There is a folder in the content tree called Books which has a list of items. Each item will have these fields - Title, Author, Price
When user searches for a term, it will be checked for a match with any of the 3 fields of the item and return the results.
This method is not working as it returns null Item.
public PartialViewResult GetSearchBooks(string txtSearch)
{
string index = string.Format("sitecore_{0}_index", Sitecore.Context.Database.Name);
List<SearchResultItem> query;
List<Item> matches = new List<Item>();
using (var context = ContentSearchManager.GetIndex(index).CreateSearchContext())
{
query = context.GetQueryable<SearchResultItem>()
.Where(p => p.Path.StartsWith("/sitecore/content/Book")).ToList();
}
foreach(SearchResultItem sritem in query)
{
Item item = sritem.GetItem(); //item is null here
if(item.Fields["Title"].Value.Contains(txtSearch) ||
item.Fields["Title"].Value.Contains(txtSearch) ||
item.Fields["Title"].Value.Contains(txtSearch))
matches.Add(item);
}
return(matches);
}
Is it the right approach. If not please suggest one.

Paths
Avoid querying the path like this:
context.GetQueryable<SearchResultItem>()
.Where(p => p.Path.StartsWith("/sitecore/content/Book"));
Instead use
context.GetQueryable<SearchResultItem>()
.Where(p => p.Paths.Contains(idOfBookFolderItem));
For more info on why, see http://blog.paulgeorge.co.uk/2015/05/29/sitecore-contentsearch-api-filtering-search-on-folder-path/
Approach
You need to hand the entire query to the search api in one go.
List<SearchResultItem> matches;
using (var context = ContentSearchManager.GetIndex(indexName).CreateSearchContext())
{
var predicate = PredicateBuilder.True<SearchResultItem>();
// must have this (.and)
predicate = predicate.And(p => p.Paths.Contains(bookFolderItem.ID));
// must have this (.and)
predicate = predicate.And(p => p.Name == searchTerm);
matches = context.GetQueryable<SearchResultItem>().Where(predicate).ToList();
}
This returns SearchResultItems not Items. If you need the item, just call GetItem.
Matches[i].GetItem()
Null items
This may indicate that your index is out of sync with the database. Try re-indexing from control panel, or in the case of the web database, REpublish the expected content.
Searching template fields
This just searches against the item name. You're limited to being able to specify the generic fields in SearchResultItem class. If you want to search specific fields on items, you can inherit from SearchResultItem and add those fields.
public class BookSearchResultItem : SearchResultItem
{
[IndexField("Book Title")]
public string BookTitle { get; set; }
[IndexField("Book Author")]
public string BookAuthor { get; set; }
}
You can then pass this into the query and search on those fields
List<BookSearchResultItem> matches;
using (var context = ContentSearchManager.GetIndex(indexName).CreateSearchContext())
{
var predicate = PredicateBuilder.True<BookSearchResultItem>();
// must have this (.and)
predicate = predicate.And(p => p.Paths.Contains(bookFolderItem.ID));
// must have this (.and)
predicate = predicate.And(
PredicateBuilder.False<BookSearchResultItem>() // in any of these fields
.Or(p => p.BookTitle == searchTerm)
.Or(p => p.BookAuthor == searchTerm)
.Or(p => p.Name == searchTerm));
matches = context.GetQueryable<BookSearchResultItem>().Where(predicate).ToList();
}
Searching all 'content'
If you find that having to specify the explicit fields is an unwanted hassle or you are performing searches across different templates with different fields, you can instead use the special computed 'content' field which combines all the text data from an item into one indexed field. So instead of the original query which did this
predicate = predicate.And(p => p.Name == searchTerm);
You can instead do just use
predicate = predicate.And(p => p.Content == searchTerm);
Which will find results where the searchterm exists in any field on the item.

First, did you check "query" contains any result?
I would suggest performing the following search query:
query = context.GetQueryable<SearchResultItem>()
.Where(p => p.TemplateId == yourBookItemTemplateID &&
(p.Fields["Title"].Value.Contains(txtSearch) ||
p.Fields["Author"].Value.Contains(txtSearch) ||
p.Fields["Price"].Value.Contains(txtSearch));
return query.Select(x => x.GetItem());

i would not suggest this approach to use. Let me explain why, or what you can do better.
First create your own Sitecore index and do not simply use the default master or web index. If you do that, you can safe the following line of code .Where(p => p.Path.StartsWith("/sitecore/content/Book")).ToList();, cause in a custom index you can simply restrict, what exactly is crawled.
Second you should never access the Sitecore item out of the search results. Reasons for that is the performance. Item item = sritem.GetItem(); You use a search, because its a performant way to access a huge amount of data. When you now access for every result the Sitecore item from the database, you lose your benefit of using a search.
You should simply use the Result Type, in your case the basic SearchResultItem. At the End of your filtering you should call something like var results = query.GetResults(); instead of accessing the items directly.
Here I found a simple example of a sitecore search, with custom index and without accessing the items directly, maybe this helps you.
http://www.mattburkedev.com/sitecore-7-contentsearch-tips/
Now to your problem.
Did you debug the search and looked into the rest of the fields of sritem? Are they all filled? If i remember correctly there is a property which stores the itemId, to retrieve the item with GetItem(). Maybe you could give us the values of the property while trying to retrieve the item.

Sometimes when the index is out of date, the returned search items may no longer exist in your content tree, So rebuild the index and try the search again,
Couple of enhancements that you can apply to your search:
As mentioned in Christian answer you can create index for just your Books tree, which means to set the root of the index to the Books root item.Web index usually used for full site content search.
Instead of getting all books items then go through all items; you can use predicates instead; even after you create the new index use the predicates to get the desired items only.
Also if your site is multilingual add a predicate to filter the required language else you will get multiple versions of the same item.

Related

Ordering Sitecore search results

I have a IQueryable<T> object as search results object.
I apply the filtering and sorting on this search object.
Before I call the GetResults(), I want to order the results based on one of the field's (Fieldname - Priority) value. So for all the items in the IQueryable<T> object, I want to order them desc by Priority field, so all the items which has a value for that field stay at the top and the rest are at the bottom.
I have the fieldmap entry for Priority field.
search.OrderByDescending(i => !string.IsNullOrEmpty(i.GetItem().GetFieldValue("Priority")))
The above command doesn't work. Apparently, I can't use Sitecore extension methods with IQueryable?
If I convert search.ToList(). Do the ordering and then convert it back to AsQueryable(), I get the following error:
There is no method 'GetResults' on type 'Sitecore.ContentSearch.Linq.QueryableExtensions'
that matches the specified arguments
Is there a neat and quick way to get around this?
Cheers
I think you just need to add your field to your SearchResultItem and mark it as an int. I am making the assumption that the field is an int. Make a custom class that inherits SearchResultItem.
public class CustomSearchResultItem : SearchResultItem
{
[IndexField("Priority")]
public int Priority { get; set; }
}
Then use it in your search. Finally order by it.
using (var context = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext())
{
var results = context.GetQueryable<CustomSearchResultItem>().Where(prod => prod.Content.Contains("search box text").OrderByDescending(t => t.Priority);
}
Some data found here.
http://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/10/sorting-and-ordering-results.aspx
You can order search results using multiple fields by using the OrderByDescending combined with ThenByDescending. So you would need to order by Priority and then by [Name|Date|Whatever].
I want to order them desc by Priority field, so all the items which has a value for that field stay at the top and the rest are at the bottom.
I sort them first on the criteria chosen by the user - like Name, Date created etc. Once I get the results back, I need to order them by priority field
You are conflicting yourself in the questions and comments. If you want the results with priority first and then by user selected results then the following will work:
query = dataQuery.OrderByDescending(i => i.Title).ThenByDescending(i => i["Priority"]);
var results = query.GetResults().Hits.Select(h => h.Document);
There was a bug in earlier version of Sitecore which meant that the ThenBy clause will be added before the OrderBy clause hence it is added in reverse above. You may want to check if this is fixed in the current version. If so simply change your query to:
query = dataQuery.OrderByDescending(i => i["Priority"]).ThenByDescending(i => i.Title);
You don't have to add the field to your SearchResultItem if you just want to order by it, only if you need the actual value of that field returned to as well.
If you need to order by a custom user supplied value then you can pass in i => i["whatever-field-the-user-has-selected"] instead of i.Title.
You can find more info in this blog post.

Sitecore TreelistEx used as a UI for multi-link layouts

I need to present a content editor interface that allows editors to select specific pages in order to generate a link list for website visitors.
It seems treelist/treelistEX is providing the expected interface and I have combined that with a source path to lock the editors to a start destination rather than the entire sitecore tree. Opted for treelist EX as this appears to be the most efficient way as it doesnt render the tree in full each time unless its called upon.
In terms of the output however I'm getting a pipe separated list of GUIDs- is this something I need to iterate through manually using linkmanager or some such to obtain the items title and its sitecore link? Or is there an existing process that manages such a multi-list and breaks it up into its components.
If anyone can provide an example of that code and how to draw out the title and URL that would be great.
Thanks
There is no built-in solution for getting title and url from the items selected in Treelist.
You can treat your Treelist field as a Multilist field (they both store just list of pipe separated IDs in the background) and use GetItems() method:
Sitecore.Data.Fields.MultilistField treelistField = Sitecore.Context.Item.Fields["myTreelistFieldName"];
Item [] selectedItems = treelistField.GetItems();
foreach (Item item in selectedItems)
{
string itemName = item.Name;
string displayName = item.DisplayName; // fallback to Name if not set
string title = item["Title"]; // assuming there is a field called Title
string url = LinkManager.GetItemUrl(item);
}

Sort 'Days' Items in Sitecore according to days of week

Is there any way to sort the following items according to the days of week.
In C# I can do something like this:
string [] initialArray = {"Friday", "Monday", ... } ;
string [] sortedArray = initialArray.OrderBy(s => Enum.Parse(typeof(DayOfWeek), s)).ToArray() ;
But I don't know how can I achieve this kind of functionality with Sitecore.
If what you really care is to display the days sorted on the front-end regardless of how they are organised in the Content Editor then simply sort them in code before you display them, e.g.
using System.Linq;
var openingHours = Sitecore.Context.Item.Children
.OrderBy(s => Enum.Parse(typeof(DayOfWeek), s.DisplayName));
If you want to sort them in the Content Editor then you need to create a custom sorter. Sitecore Climber has provided links, but for this specific example you can use:
using Sitecore.Data.Comparers;
using Sitecore.Data.Items;
public class DayOfWeekComparer : Comparer
{
protected override int DoCompare(Item item1, Item item2)
{
var x = (int)Enum.Parse(typeof(DayOfWeek), item1.DisplayName);
var y = (int)Enum.Parse(typeof(DayOfWeek), item2.DisplayName);
return x.CompareTo(y);
}
}
Then in the core database create an item of item type /sitecore/templates/System/Child Sorting under /sitecore/system/Settings/Subitems Sorting and set the type to your class.
You should set the Subitem Sorting on the Standard values of a template. In this instance it looks like you have a simple Folder template, so you would need to create a more specific template for your Opening Hours folder. Even so, the user can still decide to re-order the items OR change the default sort for that folder. The only guaranteed way to force the output is by sorting before you render, i.e. the first bit of code.
please check next articles:
http://firebreaksice.com/how-to-sort-sitecore-items-in-the-content-editor/
http://sitecore.alexiasoft.nl/2009/08/04/sorting-sitecore-items/
http://sitecoreblog.blogspot.in/2010/11/change-default-subitems-sort-order.html

Sitecore/Glass Mapper: how to return query in order of content tree

We have a site with several queries that query the direct children of a content item. We simply want just the children sorted by the order they appear in the content tree.
We're using Glass Mapper and our collection properties look like this:
[SitecoreQuery("/sitecore/content/Global/Team Members/Categories/*")]
public IEnumerable<ICategory> Categories { get; set; }
The above property Categories returns the child items in what seems to be alpha order, but in some cases it seems a bit random.
Any idea how to set up the query to pull in the order of the content tree?
Scott,
I believe that Glass is using fast query for these SitecoreQueries which is why you aren't getting any predictable sort order. Unfortunately the fast query does not allow sorting in the query syntax. You will see the same if you put your query into the XPath Builder in Sitecore's Developer Center by using this syntax:
fast:/sitecore/content/Global/Team Members/Categories/*
I think the quickest way to resolve this is to just add Sitecore's __Sortorder field to your ICategory definition.
[SitecoreField("__Sortorder"),]
string Sortorder { get; set; }
You could then add another property to your model that returns the sorted version of this.
public IEnumberable<ICategory> SortedCategories { get { return Categories.OrderBy(s=>s.Sortorder); } }
Keep in mind you can also use the [SitecoreChildren] decorator to get the children of the current item. I'm not sure if that is actually what you are after, but that decorator will actually return your items in the correct order per Sitecore's SortOrder.

Retrieving the field names from sitecore item

While I am searching the keyword in sitecore using sitecore.seach it returns the item name(where it is located) for the search keyword. Is there any option to get the field name along the item?
I use the following code:
using (IndexSearchContext context = searchIndex.CreateSearchContext())
{
SearchHits hits = context.Search(searchString, new SearchContext(SiteRoot));
var results = hits.FetchResults(0, 100);
foreach (SearchResult result in results)
{
try
{
Item item = result.GetObject<Item>();
if (item != null)
{
results.AddResultToCategory(result, categoryName);
}
}
...
}
}
If I understand your goal correctly, you want to find out the exact fields (field names) where the search string was found, right?
If that's the case, then you should get a collection of item fields (item.Fields), and iterate through it checking for the search string in the field value.
As far as I know, the Sitecore Search shell application works the same way when displaying results: the item is taken form the SearchResult, and the field collection is just iterated looking for the hit using simple text.IndexOf() comparison.
And I'm not aware about a different way to accomplish this.