SolrNet facetted search - c#

I'm using Solr and Solrnet for the first time.
I have managed to create a schema, which has amongst them the following fields:
created_date DateTime
parent_id int
categories string multi-valued
I've been able to get a basic search working against the parent_id field, as so:
var solr = ServiceLocator.Current.GetInstance<ISolrOperations<SolrNode>>();
ICollection<SolrNode> results = solr.Query(new SolrQueryByField("parent_id", currentNode.Id.ToString()));
I've been able to figure out (kind of) how to return facets for all of my results, as so:
var solrFacet = ServiceLocator.Current.GetInstance<ISolrOperations<SolrNode>>();
var r = solrFacet.Query(SolrQuery.All, new QueryOptions
{
Rows = 0,
Facet = new FacetParameters
{
Queries = new[] {
//new SolrFacetDateQuery("created_date", DateTime.Now /* range start */, DateTime.Now.AddMonths(-6) /* range end */, "+1DAY" /* gap */) {
// HardEnd = true,
// Other = new[] {FacetDateOther.After, FacetDateOther.Before}
//}
new SolrFacetDateQuery("created_date", new DateTime(2011, 1, 1).AddDays(-1) /* range start */, new DateTime(2014, 1, 1).AddMonths(1) /* range end */, "+1MONTH" /* gap */) {
HardEnd = true,
Other = new[] {FacetDateOther.After, FacetDateOther.Before}
}
//,
//new SolrFacetFieldQuery("categories")
},
}
});
//foreach (var facet in r.FacetFields["categories"])
//{
// this.DebugLiteral.Text += string.Format("{0}: {1}{2}", facet.Key, facet.Value, "<br />");
//}
DateFacetingResult dateFacetResult = r.FacetDates["created_date"];
foreach (KeyValuePair<DateTime, int> dr in dateFacetResult.DateResults)
{
this.DebugLiteral.Text += string.Format("{0}: {1}{2}", dr.Key, dr.Value, "<br />");
}
But what I'm not able to figure out is how to plumb it all together. My requirements are as follows:
Page loads - show all search results where parent_id matches N. Query facets of search results and show tick boxes for the facets like so:
Categories
Category 1
Category 2
Within
Last week
Last month
Last 3 months
Last 6 months
All time
User clicks on relevant tick boxes and then code executes another solr query, passing in both the parent_id criteria, along with the facets the user has selected.
I realise in my description I have simplified the process, and perhaps it is quite a big question to ask on StackOverflow, so I'm of course not expecting a working example (although if you're bored pls feel free ;-)) but could anyone provide any pointers, or examples? SolrNet does have an MVC sample app, but I'm using WebForms and not particularly comfortable with MVC just yet.
Any help would be greatly appreciated.
Thanks in advance
Al

You can club the Query and add facets as Query Opertations
ISolrQueryResults<TestDocument> r = solr.Query("product_id:XXXXX", new QueryOptions {
FacetQueries = new ISolrFacetQuery[] {
new SolrFacetFieldQuery("category")
}
});

I create a 'fq' string of the facets selected. For instance, if the user selects these facets:
united states
california
almond growers
and the facet field is 'content_type', I generate this query string:
(content_type:"united states" AND content_type:california AND content_type:"almond growers")
Note the quotes and the open and close parenthesis...important! I store this in the variable named finalFacet. I then submit it to Solr like this, where sQuery is the text the user is searching on and finalFacet is the facet query string as shown above:
articles = solr.Query(sQuery, new QueryOptions
{
ExtraParams = new Dictionary<string, string> {
{"fq", finalFacet},
},
Facet = new FacetParameters
{
Queries = new[] { new SolrFacetFieldQuery("content_type")}
},
Rows = 10
Start = 1,
});

Related

How to search based on field in azure search?

i am using a azure search, and i have a console app with the code as below, which is working fine.
DocumentSearchResult<Hotel> results;
Console.WriteLine("Search started\n");
results = indexClient.Documents.Search<Hotel>("smart", new SearchParameters { Top=5 });
WriteDocuments(results);
currently its searching a text with word "smart". this is straight forword, what i need is i have several fields in the table, i want to search based on the feild .
for example let i have two fields
1)Title
2)SoldDate
I have to write code for finding items which has title 'john' and which has a sold date < current date.
what should i do to achieve this?
You can achieve what you want with search and a filter:
// Approach #1
string currentDate = DateTime.UtcNow.ToString("O");
var parameters = new SearchParameters()
{
Filter = "soldDate lt " + currentDate,
Top = 5
}
results = indexClient.Documents.Search<Hotel>("john", parameters);
This will filter the documents to only those with a soldDate before currentDate, and then searches the filtered documents such that documents match if any of the searchable fields contain "john". You can narrow this down to just the title field like this:
// Approach #2
string currentDate = DateTime.UtcNow.ToString("O");
var parameters = new SearchParameters()
{
Filter = "soldDate lt " + currentDate,
SearchFields = new[] { "title" },
Top = 5
}
results = indexClient.Documents.Search<Hotel>("john", parameters);
Or like this:
// Approach #3
string currentDate = DateTime.UtcNow.ToString("O");
var parameters = new SearchParameters()
{
Filter = "soldDate lt " + currentDate,
QueryType = QueryType.Full,
Top = 5
}
results = indexClient.Documents.Search<Hotel>("title:john", parameters);
Which way you use depends on whether you want all search terms to be limited to a specific set of fields (Approach #2), or if you want specific terms to match specific fields (Approach #3).
The reference for SearchParameters is on learn.microsoft.com.

Querying a list of strings with a query string?

I have a dictionary:
<string,List<string>>
The key is the product code say "product1" then the list is a list of properties:
"Brand","10.40","64","red","S"
Then I 'can' have a list of rules/filters e.g.
var tmpFilter = new customfilters();
tmpFilter.Field = "2";
tmpFilter.Expression = ">";
tmpFilter.Filter = "10";
So for the above example this would pass because at index 2 (tmpFilter.Field) it is more than 10; then I have another object which defines which fields within the list I want to write to file. For that dictionary item I just want to write the product brand and price where the filters match.
At the moment without the filter I have:
var tmp = new custom();
tmp.Columns = "0,1";
tmp.Delimiter = ",";
tmp.Extention = ".csv";
tmp.CustomFilters = new List<customfilters>() {new customfilters(){ Field = "2", Expression = ">", Filter = "10"} };
public static void Custom(custom custom)
{
foreach (var x in Settings.Prods)
{
//Get Current Product Code
var curprod = Settings.ProductInformation[x];// the dictionary value
foreach (var column in custom.Columns)
{
var curVal = curprod[Convert.ToInt32(column)];
tsw.Write(curVal + custom.Delimiter);
}
Settings.Lines++;
tsw.WriteLine();
}
tsw.Close();
}
I only want to write the curprod if all the filters pass for that list of strings.
How I can do this?
There's a really nice Nuget package based on an example published by Microsoft, that they have decided to make really hard to find for some reason, that allows dynamic linq queries:
https://www.nuget.org/packages/System.Linq.Dynamic/1.0.2
Source:
https://github.com/kahanu/System.Linq.Dynamic
Using that you can do stuff like this very easily (note: I used strings here because the OP states they have a List<string>):
List<string> stuff = new List<string> { "10.40", "64", "5", "56", "99", "2" };
var selected = stuff.Select(s => new { d = double.Parse(s) }).Where("d > 10");
Console.WriteLine(string.Join(", ", selected.Select(s => s.d.ToString()).ToArray()));
Outputs:
10.4, 64, 56, 99
That may give you a place to start. One thing you are going to have to tackle is identifying which of your fields are numeric and should be converted to a numeric type before trying to apply your filter. Otherwise you are going to comparing as strings.

Lucene.Net to weigh descriptions with the same word in it multiple times, higher score

I am trying to figure out how I can add more weight to a description that has the same word multiple times in it to appear first for the lucene.net in c#.
Example:
Pre-condition:
Lets say I have a list of items like this:
Restore Exchange
Backup exchange
exchange is a really great tool, exchange can have many mailboxes
Scenario:
I search for exchange.
The list would be returned in this order:
(it has the same weight as 2 and it was added to the index first)
(it has the same weight as 1 and it was added to the index second)
(has a reference of exchange in it, but its length is greater then 1 and 2)
So I am trying to get #3 to show up first as it has exchange in the description more then one time.
Here is some code showing that I set the Similarity:
// set up lucene searcher
using (var searcher = new IndexSearcher(directory, false))
{
var hits_limit = 1000;
var analyzer = new StandardAnalyzer(Version.LUCENE_29);
searcher.Similarity = new test();
// search by single field
if (!string.IsNullOrEmpty(searchField))
{
var parser = new QueryParser(Version.LUCENE_29, searchField, analyzer);
var query = parseQuery(searchQuery, parser);
var hits = searcher.Search(query, hits_limit).ScoreDocs;
var results = mapLuceneToDataList(hits, searcher);
analyzer.Close();
searcher.Dispose();
return results;
}
// search by multiple fields (ordered by RELEVANCE)
else
{
var parser = new MultiFieldQueryParser
(Version.LUCENE_29, new[] { "Id", "Name", "Description" }, analyzer);
var query = parseQuery(searchQuery, parser);
var hits = searcher.Search
(query, null, hits_limit, Sort.RELEVANCE).ScoreDocs;
var results = mapLuceneToDataList(hits, searcher);
analyzer.Close();
searcher.Dispose();
return results;
}
Disclaimer: I can only speak about Lucene (and not Lucene.NET) but I believe they are built using the same principles.
The reason why documents #1 & #2 come up first is because field weights (1/2 for #1, 1/2 for #2) are higher than 2/11 for #3 (assuming you are not using stop words). The point here is that "exchange" term in first two documents has far more weight than in the third where it's more diluted. This is how default similarity algorithm works. In practice this is a bit more complex, as you can observe in the given link.
So what you are asking for is an alternative similarity algorithm. There's a similar discussion here where MySim, I believe, attempts to achieve something close to what you want. Just don't forget to set this similarity instance to both index writer and searcher.

Need a Recursive Function to Solve a Typical Ecommerce Multi-Variation Problem

Let's say I have the following data (in pseudo-code for readability):
var myVariations = [
{ Name = "Color", Values = ["Red", "Yellow", "Green" /*, etc. */] },
{ Name = "Size", Values = ["S", "M", "L" /*, etc. */] },
{ Name = "Length", Values = ["34", "35", "36" /*, etc. */] },
/* and so on...(up to 5 total) */
];
And I can get that data with LINQ like so:
var myVariations = myProduct.Variations.ToList();
How can I go about mapping those variations into a structure like this (for the eBay Trading API):
var ebayVariations = [
{
Name = "Red-S-34",
Value = [
// yes, these are arrays with only one item
{ Name = "Color", Values = [{Value = "Red"}] },
{ Name = "Size", Values = [{Value = "S"}] },
{ Name = "Length", Values = [{Value = "34" }] }
]
},
/* etc for all possible combinations */
];
Obviously the fact that the Values array holds only one value is a bit strange; but with eBay's Trading API if I list multiple values in a single Variation (which is easy to do compared to this recursive stuff) it complains. So alternatively, if you are familiar with the eBay Trading API, how can I get this to work in an "optimal" fashion, in-line with the way eBay intended Variations to be listed (called via AddFixedPricedItem, if you care).
I don't know anything about the eBay Trading API, but here's an article on computing a Cartesian Product with LINQ (the very last step drops the recursion in favor of aggregation).
I've changed terminology insignificantly, but wrote clarifying comments.
public IEnumerable<Combination> GetCombinations(Variation[] variations, int variationIndex, IEnumerable<VariationPosition> aggregatedPositions)
{
// We should choose one position from every variation,
// so we couldn't produce combination till we reach end of array.
if (variationIndex < variations.Length)
{
// Pick current variation.
var currentVariation = variations[variationIndex];
// Every variation has list of possible positions (Color could be Green, Redm, Blue, etc.).
// So we should walk through all the positions
foreach (var val in currentVariation.Positions)
{
// Current position. Variation's name will be used during creating result Combination.
var position = new VariationPosition()
{
Name = currentVariation.Name,
Value = val
};
// Add position to already aggregated on upper levels of recursion positions.
var newPositions = aggregatedPositions.Concat(Enumerable.Repeat(position, 1));
// So we picked some variation position
// Let's go deeper.
var combinations = this.GetCombinations(variations, variationIndex + 1, newPositions );
// This piece of code allows us return combinations in iterator fashion.
foreach (var combination in combinations)
{
yield return combination;
}
}
}
else
{
// We reached end of variations array
// I mean we have one position of every variation.
// We concatenate name of positions in order to create string like "Red-S-34"
var name = aggregatedPositions.Aggregate("", (res, v) => res += v.Name);
// This code is a little bit naive, I'm too lazy to create proper infrastructure,
// But its mission is to create content for property Value of your ebayVariations item.
var value = aggregatedPositions
.Select(v => new { Name = v.Name, Values = new[] { new { Value = v.Value } } })
.ToArray();
// And we return completed combination.
yield return new Combination()
{
Name = name,
Value = value,
};
}
}
And usage:
var allCombinations = this.GetCombinations(inputVariations, 0, new VariationPosition[0]).ToArray();

How do I order a sql datasource of uniqueidentifiers in Linq by an array of uniqueindentifiers

I have a string list(A) of individualProfileId's (GUID) that can be in any order(used for displaying personal profiles in a specific order based on user input) which is stored as a string due to it being part of the cms functionality.
I also have an asp c# Repeater that uses a LinqDataSource to query against the individual table. This repeater needs to use the ordered list(A) to display the results in the order specified.
Which is what i am having problems with. Does anyone have any ideas?
list(A)
'CD44D9F9-DE88-4BBD-B7A2-41F7A9904DAC',
'7FF2D867-DE88-4549-B5C1-D3C321F8DB9B',
'3FC3DE3F-7ADE-44F1-B17D-23E037130907'
Datasource example
IndividualProfileId Name JobTitle EmailAddress IsEmployee
3FC3DE3F-7ADE-44F1-B17D-23E037130907 Joe Blo Director dsd#ad.com 1
CD44D9F9-DE88-4BBD-B7A2-41F7A9904DAC Maxy Dosh The Boss 1
98AB3AFD-4D4E-4BAF-91CE-A778EB29D959 some one a job 322#wewd.ocm 1
7FF2D867-DE88-4549-B5C1-D3C321F8DB9B Max Walsh CEO 1
There is a very simple (single-line) way of doing this, given that you get the employee results from the database first (so resultSetFromDatabase is just example data, you should have some LINQ query here that gets your results).
var a = new[] { "GUID1", "GUID2", "GUID3"};
var resultSetFromDatabase = new[]
{
new { IndividualProfileId = "GUID3", Name = "Joe Blo" },
new { IndividualProfileId = "GUID1", Name = "Maxy Dosh" },
new { IndividualProfileId = "GUID4", Name = "some one" },
new { IndividualProfileId = "GUID2", Name = "Max Walsh" }
};
var sortedResults = a.Join(res, s => s, e => e.IndividualProfileId, (s, e) => e);
It's impossible to have the datasource get the results directly in the right order, unless you're willing to write some dedicated SQL stored procedure. The problem is that you'd have to tell the database the contents of a. Using LINQ this can only be done via Contains. And that doesn't guarantee any order in the result set.
Turn the list(A), which you stated is a string, into an actual list. For example, you could use listAsString.Split(",") and then remove the 's from each element. I’ll assume the finished list is called list.
Query the database to retrieve the rows that you need, for example:
var data = db.Table.Where(row => list.Contains(row.IndividualProfileId));
From the data returned, create a dictionary keyed by the IndividualProfileId, for example:
var dic = data.ToDictionary(e => e.IndividualProfileId);
Iterate through the list and retrieve the dictionary entry for each item:
var results = list.Select(item => dic[item]).ToList();
Now results will have the records in the same order that the IDs were in list.

Categories