Linq is not working with sitecore solr - linq

I am displaying search results using sitecore and solr. The scenario is if user search for something, search code first will check if that keyword is in "keywords" field of sitecore items, if it finds it will show the result and if it does not find, it will check for that keyword in title and description fields.
Now the problem is -
below condition is always false and never gives the result, in spite of values in the keywords field.
var Query = searchContext.GetQueryable<SearchResultItem>()
.Where(i => (i["keywords"].Contains(SearchQuery)));
where as the same query for title and description works fine
var Query2 = searchContext.GetQueryable<SearchResultItem>()
.Where(i => (i["title"].Contains(SearchQuery)
|| i["description"].Contains(SearchQuery)));
for each sitecore item, I have title, description and keywords fields.
Below is the detail code snippet.
public class SearchModel
{
public string SearchQuery { get; set; }
List<WebSiteSearchResult> lst = new List<WebSiteSearchResult>();
public IEnumerable<WebSiteSearchResult> SiteSearchResult()
{
var searchContext = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext();
var Query = searchContext.GetQueryable<SearchResultItem>().Where(i => (i["keywords"].Contains(SearchQuery)));
var result = Query.GetResults();
if (result.TotalSearchResults != 0)
{
//some operations
}
else
{
var Query2 = searchContext.GetQueryable<SearchResultItem>().Where(i => (i["title"].Contains(SearchQuery) || i["description"].Contains(SearchQuery)));
var result2 = Query2.GetResults();
if (result2.TotalSearchResults != 0)
{
//some operations
}
}
lst = lst.OrderBy(i => i.itemBoostingRatio).ToList();
return lst;
}
}
public class WebSiteSearchResult
{
public string itemKeywords { get; set; }
public int itemBoostingRatio { get; set; }
public string itemTitle { get; set; }
public string itemDescription { get; set; }
}
And, here my sitecore items: http://i.stack.imgur.com/liw59.png

Given your Sitecore item keywords are in a SLT field ("Loans, Car Loan, Bike Loan"), keep in mind that this field is a tokenized index. This means during storage processing, the query is split into tokens using whitespace and punctuation characters as delimiters, seen below:
Now if we search using Equals and Contains, the search log will give us the appropriate serialized query to test in solr:
Equals: ?q=keywordstest_t:("bike loan")
This is telling us to find any term in the list that matches this given value. If our term was "scooter loan", we would not get any results back.
Contains: ?q=keywordstest_t:(*bike loan*)
This is telling us to return any result item where any term in the list contains any of the words/sub-words. This isn't search performant. If our term was "scooter L", we would get a result back because of the L.
A couple things to check:
Is this value set to true in the Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config?
<indexAllFields>true</indexAllFields>
For best practices, create a new Search Result Item class that inherits from Sitecore's base SearchResultItemso that you can use it this instance to directly call the field property 'Keywords':
public class BasePageSearchResultItem : SearchResultItem
{
[IndexField("keywords")]
public string Keywords { get; set; }
}
The Equals query should be used to find one of these terms in the indexed field:
var Query = searchContext.GetQueryable<SearchResultItem>()
.Where(i => i.Keywords.Equals(SearchQuery));
The result set will depend on the requirements, dictated by the search query "scooter loan" and given a sample item's keyword field value "Loans, Car Loan, Bike Loan":
Search on scooter and loan separately, where each search query term is an exact match or is contained in an indexed term
Search on scooter loan as a whole

Related

Count the occurrences of an object in a list where multiple criteria are matching using LINQ

I have a list of Cutdetails. I am trying to write a function using LINQ that will return the count of bars in the list where the CODE , BRAND, CODE and LENGTH all match. I want to be able to specify all these parameters and return a number for the number of matches.
I have tried using foreach statements which is fine but i'm sure there is an neater and smarter way to do it using LINQ. Any suggestions?
List<Bar> bars = new List<Bar>();
public class Bar
{
public string Brand { set; get; }
public string System { set; get; }
public string Code { set; get; }
public string Length { set; get; }
}
Thanks in advance!
Will
You can filter using the match and then do a count.
var occurences = bars.Where(x => x.Brand == "Brand" && x.Code == "code").Count();

LINQ search Product Index

I have a List<Product> contains properties of Bikes (Name, ProductName, Color, List Price). I'm struggling to figure out how to write a search function using LINQ. I'd like to find a name of Bike. Any suggest will be help me some ways.
Imagine that your name is taken from a variable called nameToSearch.
This is if you want to get the Product.
string nameToSearch = "BikeName";
List<Product> list = bikes.Where(x => x.Name == nameToSearch).ToList();
I assume you have the following Product class:
public class Product
{
public String Name { get; set; }
public String ProductName { get; set; }
public String Color { get; set; }
public String List { get; set; }
public String Price { get; set; }
}
You also mentioned you have your data in a List<Product>. I will give a demo name for it:
List<Product> myProductList = GetProductList();
// Where GetProductList() will create a new List<Product> and populate it.
String bikeNameFilter = GetNameFilter();
// You can chnage this by the string you want for filtering.
You can use the following to get your data:
List<Product> myFilteredProductList = (from p in myProductList
where p.Name = bikeNameFilter
select p;
).ToList()
Obviously you can change the filter you want to use to another property of your product. Finally to get the actual name, you can loop through the list you just got:
foreach (var p in myFilteredProductList)
{
Console.WriteLine(p.ProductName);
// Use this value wherever you want.
}
Take a look at a nuget package I have created
http://www.nuget.org/packages/NinjaNye.SearchExtensions
This will enable the following (and more) which will return results where the search term appears in any of the properties specified
var result = products.Search("searchTerm", p => p.Name, p => p.ProductName);
Performing a search against all string properties can be done as follows:
var result = products.Search("searchTerm");
Alternatively, you can perform an AND search where the search term exists in a set of properties as follows:
string searchTerm = "searchTerm";
var result = products.Search(searchTerm, p => p.Name)
.Search(searchTerm, p => p.ProductName);
For more information take a look at the projects GitHub page or my blog posts
UPDATE: don't forget the using directive...
using NinjaNye.SearchExtensions

Linq Grouping looses the child entities

I have the following query:
var _customers = (from c in _db.UserProfiles.Include(x=>x.ParentCompanies).Include(x=>x.cProfile).Include(x=>x.cProfile.PhoneNumbers).Include(x=>x.cProfile.Addresses)
where (c.ParentCompanies.Any(pc => pc.CompanyUsers.Any(cu => cu.UserName == userName)) && c.cProfile != null)
group c by c.FirstName.Substring(0, 1).ToUpper() into customerGroup
select new ContactsViewModel
{
FirstLetter = customerGroup.Key,
Customers = customerGroup
}).OrderBy(letter => letter.FirstLetter);
if I take out the group, it works well and includes all the children (parentCompanies, cProfile, ...) as soon as I put the group back in it looses all of the children. How do I solve this issue?
update
I guess I should also include the view model that I'm usign to put the result in.
public class ContactsViewModel
{
public string FirstLetter { get; set; }
public IEnumerable<UserProfile> Customers { get; set; }
}
Include only applies to items in the query results (i.e. the final projection) and cannot contain operations that change the type of the result between Include and the outermost operation (e.g. GroupBy())
http://wildermuth.com/2008/12/28/Caution_when_Eager_Loading_in_the_Entity_Framework
If you want to eager load, do the grouping client-side (i.e. enumerate the query then call the GroupBy method on the results)

Can I query for and retrieve members of a collection property in RavenDB using full text search?

I'm using the term "child documents" loosely relating to objects stored in a collection property. Given those two classes:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Bar> Bars { get; set; }
}
public class Bar
{
public string Name { get; set; }
public string Description { get; set; }
public string SomeString { get; set; }
public int SomeInt { get; set; }
}
I would like to query for Bars which have the term "roses" in either their Name or Description properties. Please note that Bars are stored within Foo.
This is a two fold question:
Can a query be used over a collection of type Foo to return Bars ("child document")? I want to get a collection of Bars which have "roses" in their Name, other Bars should be skipped. I don't want Foos (aggregate root) returned.
If so, how to do it with full text search?
Regarding #1, I know something like that is impossible in MongoDB, where I would either have to store Foo and Bar separately or query for Foos where any of their Bars has "roses" in the Name and then do something about it on the client. But RavenDB has Live Projections / TransformResults, so I thought it would be possible perhaps.
You can store a copy of each Bar in field storage with the index, so yes - it can be done. But you should make sure you understand the impact of doing this.
Normally when you query from raven, the index is only used to determine which documents get sent back. The documents themselves don't come from the index, but from the document store. The document store has ACID guarantees - meaning you will always get the most current copy of the document regardless of the state of the index. If you project from index entries or index field storage, then the values you get back are as stale as the index itself.
Say you are constantly updating the Bars, and you search before the index has caught up to your last update. You could get back a Bar that has old data. Therefore, you need to weigh in the potential staleness of data into your results, possibly using one of the WaitForNonStaleResultsAsOf... customizations - which will slow the speed your search results return if you have lots of writes going on.
public class Foos_BarsByName
: AbstractIndexCreationTask<Foo, Foos_BarsByName.Result>
{
public class Result
{
public string Name { get; set; }
public Bar Bar { get; set; }
}
public Foos_BarsByName()
{
Map = foos => from foo in foos
from bar in foo.Bars
select new
{
bar.Name,
Bar = bar
};
Index(x => x.Name, FieldIndexing.Analyzed);
Index(x => x.Bar, FieldIndexing.No);
Store(x => x.Bar, FieldStorage.Yes);
}
}
var results = session.Query<Foos_BarsByName.Result, Foos_BarsByName>()
.Customize(x => x.WaitForNonStaleResultsAsOfNow())
.Search(x => x.Name, "roses")
.Select(x => x.Bar);
Another way to handle it might be to let all Foos come back, and then pull out the Bars you are interested in on the client side. At least then, everything comes from the document store:
public class Foos_BarsByName
: AbstractIndexCreationTask<Foo, Foos_BarsByName.Result>
{
public class Result
{
public string Name { get; set; }
}
public Foos_BarsByName()
{
Map = foos => from foo in foos
from bar in foo.Bars
select new
{
bar.Name
};
Index(x => x.Name, FieldIndexing.Analyzed);
}
}
var results = session.Query<Foos_BarsByName.Result, Foos_BarsByName>()
.Search(x => x.Name, "roses")
.As<Foo>()
.AsEnumerable()
.SelectMany(x => x.Bars)
.Where(x => x.Name.IndexOf("roses",
StringComparison.CurrentCultureIgnoreCase)
!= -1)
.AsEnumerable() will force the linq-to-raven query to execute, making everything that follows happen in linq-to-objects on the client side.
Of course, if you are doing a more advanced search than can be expressed with c# string functions, then you won't be able to take this second approach.

RavenDB: Indexing blog-post tags from multiple blogs

How do I created the appropriate AbstractIndexCreationTask for the following scenario?
For a scenario of multiple blogs, how do I get the tags from specific blogs and the tag-count for these?
Members of interest for data-structure stored in RavenDB:
public class BlogPost {
public string BlogKey { get; set; }
public IEnumerable<string> Tags { get; set; }
/* ... */
}
The method I need to implement has the following signature:
public Dictionary<string, int> GetTagsByBlogs(string tag, params string[] blogKeys)
In normal LINQ I would write something like this:
var tags = from post in blogPosts
from tag in post.Tags
where blogKeys.Contains(post.BlogKey)
group tag by tag into g
select new {
Tag = g.Key,
Count = g.Count(),
};
But neither SelectMany or GroupBy are supported in RavenDB. I've tried different combinations for a map-reduce solution, but I can't figure out how to do this since the map and the reduce differ in data-structure.
How to create a tag cloud is described in the knowledge base of RavenDB.
In your case, you have to include the BlogKey in the index, especially in the group by clause:
public class Tags_Count : AbstractIndexCreationTask<BlogPost, Tags_Count.ReduceResult>
{
public class ReduceResult
{
public string BlogKey { get; set; }
public string Name { get; set; }
public int Count { get; set; }
}
public Tags_Count()
{
Map = posts => from post in posts
from tag in post.Tags
select new {
BlogKey = post.BlogKey,
Name = tag.ToString().ToLower(),
Count = 1
};
Reduce = results => from tagCount in results
group tagCount by new {
tagCount.BlogKey,
tagCount.Name } into g
select new {
BlogKey = g.Key.BlogKey,
Name = g.Key.Name,
Count = g.Sum(x => x.Count)
};
Sort(result => result.Count, SortOptions.Int);
}
}
Then query that index with the desired BlogKey:
var result = session.Query<Tags_Count.ReduceResult, Tags_Count>()
.Where(x => x.BlogKey = myBlogKey)
.OrderByDescending(x => x.Count)
.ToArray();
If you need to query for multiple blogs, you can try this query:
var tagsByBlogs = session.Query<Tags_Count.ReduceResult, Tags_Count>()
.Where(x => x.BlogKey.In<string>(blogKeys))
.OrderByDescending(x => x.Count)
.ToArray();
AFAIK that is as far as you can get with an index. You still have to aggregate the results on the client side as you did in your original question, except that you can skip the filtering on blogKeys:
var tags = from tag in tagsByBlogs
group tag by Name into g
select new {
Tag = g.Key,
Count = g.Count(),
};
Take a look at faceted search, you can specify the criteria at query time, like so:
var facetResults = s.Query<BlogPost>("BlogIndex")
.Where(x => x.BlogKey == "1" || x.BlogKey == "5" ...)
.ToFacets("facets/BlogFacets");
Then the grouping (and counts) is done on all the results that match the where clause.
Update You'll need an index that looks something like this:
from post in blogPosts
from tag in post.Tags
select new
{
post.BlogKey
Tag = tag
}

Resources