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.
Related
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.
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.
I have two fields that run throughout a website that I would like to match so that when a user inputs a value either of the fields, it will match the other field. I'm using Sitecore Rocks and am trying to use a query to do this.
select ##h1#, ##Title#
from /sitecore/Content/Home//*[##h1# !="##Title#"];
update set ##h1# = ##Title# from /sitecore/Content/Home//*[##Title# = "<id>"];
What am I missing here?
This article talks about tapping in to the item:saving event which allows you to compare the fields values of the item before and after the changes:
http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2010/11/Intercepting-Item-Updates-with-Sitecore.aspx
Using this, you can determine which field has been amended, then change the other to match.
I've had to do something similar to this when a new field was added, and we wanted to set the initial value equal to an existing field. It may be a bug in Sitecore Rocks, but I found it would only update a field when a static value was part of the query.
When I ran ##h1# = ##Title#, the query analyzer would return the correct number of items updated, but no values were actually updated. However, ##h1# = '<id>' worked perfectly. After trying a number of things, I found this did what I wanted.
update set ##h1# = '' + ##Title# from /sitecore/Content/Home//*[##Title# = "<id>"];
I hope that helps.
I have a use case in which users need to select a field value from a droplist of items. The problem with this is that the droplist needs to be dynamically built on each item (all with the same template) to only show items in a folder that have a field value equal to that of the current item's ID. In case you're already lost, here's an example of the structure:
- sitecore
- content
- Home
- ContentItem1 (with droplist)
- Site Data
- SelectableItem1(ContentItem1 selected in 'itemid' field)
- SelectableItem2(ContentItem1 selected in 'itemid' field)
- SelectableItem3(ContentItem1 not selected in 'itemid' field)
- SelectableItem4(ContentItem1 not selected in 'itemid' field)
- templates
- ContentItem1Template
- Droplist field (source set to below query)
I want my query to assign the ContentItem1's droplist field source dynamically by getting a list of items that have ContentItem1's id as their 'itemid' field's value, but by comparing the field value to that of the ContentItem1 id. I have tried doing this by comparing the field's value to the id token, like so:
query:/sitecore/content/Site Data/*[##itemid#=$id]
No matter what value I try for id ('$id', $id, #id, '#id', ##id, '##id', etc.) it does not want to resolve on the item level. Is there some way to do this so that I can reuse this ContentItem1Template for all of my items that need the same functionality?
If you are using Sitecore 7 then you can use coded field datasources. This will allow you to use any custom logic you like to specify the items which should appear in your lists.
Create a class that implements IDataSource and the ListQuery() method that returns a list of Items as the source of your field. Then set the source of your field to your method with the code: prefix, e.g. code:MyProject.Custom.FieldDataSource,MyProject.Custom
using System;
using Sitecore.Buckets.FieldTypes;
using Sitecore.Data.Items;
namespace MyProject.Custom
{
public class FieldDataSource : IDataSource
{
public Item[] ListQuery(Item item)
{
var root = item.Database.GetItem("/sitecore/content/my-item");
// some other logic to filter your item set
return root.Children.ToArray();
}
}
}
These articles should help you:
Custom Classes as Data Template Field Sources
Having code as your field its datasource
You may need to wrap the ID in single quotes like so:
query:/sitecore/content/#Site Data#/*[#itemid='$id']
That said, this seems like a good fit for using the Sitecore Link Database. Whenever you associate a SelectableItem to a ContentItem, Sitecore will store that relationship in the Link database (as long as you reference it using a field that supports it, such as a DropLink, DropTree, GeneralLink, etc.).
From there, you can use Globals.LinkDatabase.GetReferrers(contentItem) or contentItem.Links.GetValidLinks() to get a list of all referring items to the content item. This is where you can filter down the list by template ID to ensure that you only return SelectableItems.
Is there a way by using XPath Builder under Developer Center inside Sitecore Shell (a Fast Query interface) to select a particular attribute from an item. Example:
/sitecore/content/Home/Products/*[##templatename = 'Product Group']/#id
I would expect to see a collection of id's to be returned where id is an attribute of an item. If yes is it possible to extract an attribute with a space bar? Example:
/sitecore/content/Home/Products/*[##templatename = 'Product Group']/#more info
EDIT
The thing that I want to achieve is to get a collection of items (I have few hounded items here), not one particular item. That's why I am not interested in adding additional conditions, like specific item id or title. I want to see a collection of values of a specific attribute. As in example showed above, I want to see a collection of values that are assign to 'more info' attribute. Once again I am expecting to see few hounded different values that are set to 'more info' attribute.
EDIT2
There is a problem with a production, a critical stuff. There is no access to it other then thru Sitecore shell, but I don't have permissions to add/install additional packages. I know how to get this info by implementing custom code, or queering db directly, but I simply do not have permission to do it. Guys that will be able to grant me need credentials will wake up in 6 hours, so I was hoping to do whatever I can to analyse the situation. I would accept Maras answer if it was an answer not a comment - there is no way I can do it using fast query. thanks for help.
Try using #
/sitecore/content/Home/Products/*[##templatename = 'Product Group']/##more info#
This is the way around when selecting items with fields that contain spaces. Having said that I don't know if you would be able to get a specific result or not for your specific question but give it a try.
For example, consider this query which returns Product S1
fast:/sitecore/content/home/*[#Title = 'Item 1' and ##templatename = 'Product Group1']//*[#Title = 'Product S1' and ##id = '{787EE6C5-0885-495D-855E-1D129C643E55}']
However, if you place the special attribute (i.e. ##id) at the beginning of the condition, the query will not return a result.
fast:/sitecore/content/home/*[##templatename = 'Product Group1' and #Title = 'Product S1']//*[##id = '{787EE6C5-0885-495D-855E-1D129C643E55}' and #Title = 'Product S1']
Remember this, Sitecore Fast Query only supports the following special attributes:
##id
##name
##key
##templateid
##templatename
##templatekey
##masterid
##parentid
Let us know if this helps.