Nest Elasticsearch, combining bool query of Must and Should - c#

I want to filter a group of documents by Year, Format and Content.
n pseudo-SQL:
SELECT * FROM /test/document
WHERE
((format=f1|| format=f2||...|| format=fn) AND
(Year!=2013 AND Year!=2015) AND
(content like %c1% || content like %c2% ||...|| content like %cn%))
As you see, the number of formats and content items are dynamic and will be chosen by user.
So far, I figured out how to make dynamic query for each field separately and then combine them using bool query like the code below;
// For YEAR
QueryContainer qYear=null;
foreach (var year in years)
{
qYear |= new TermQuery() { Field = "year", Value = year };
}
// For FORMAT
var qformat = new List<QueryContainer>();
foreach (var format in txtDocs)
{
qformat.Add(Query<Document>.Match(m => m.OnField(p => p.Format).Query(format)));
}
// For CONTENT
var qc = new List<QueryContainer>();
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(discpString).Boost(2)));
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(roleString)));
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(taskString)));
qc.Add(Query<Document>.Match(m => m.OnField(p => p.File).Query(sysString).Boost(2)));
//MY QUERY
var searchResults = client.Search<Document>(s => s.Fields(f => f.Title, f => f.Format, f => f.Year, f => f.DateCreated, f => f.Id, f => f.Path, f => f.Size, f => f.Firstwords).
Index(defaultIndex).From(0).Size(100).
Query(q => q.Bool(b => b.MustNot(qYear).Should(qformat.ToArray()).Should(qc.ToArray()))));
When I run this code, the results for year and content field is what I expect but other formats that are not in the filtered list are also included! I want it to just retrieve those documents with the selected formats.
Does anyone knows where is my mistake?

I could find where was my mistake!
I used Querycontainer for format in the same command as what I used for Year and then used Must in my query. Here is the changed part of the code:
// For FORMAT
QueryContainer qF=null;
foreach (var format in txtDocs)
{
qF |= new TermQuery()
{
Field = "format",
Value = format
};
}
//MY QUERY
var searchResults = client.Search<Document>(s => s
.Fields(
f => f.Title,
f => f.Format,
f => f.Year,
f => f.DateCreated,
f => f.Id,
f => f.Path,
f => f.Size,
f => f.Firstwords)
.Index(defaultIndex)
.From(0)
.Size(100)
.Query(q => q
.Bool(b => b.MustNot(qYear)
.Must(qF)
.Should(qc.ToArray())
)
);

Related

How set default value for field that get from ElasticSearch using NEST

Using NEST I attempt set default value for IndexName field but it always is null
int month = startDate.Value.Date.Month;
var indexName = $"oem_catalog-0{month}.{startDate.Value.Date.Year}";
if (month < 10)
{
indexName = $"oem_catalog-0{month}.{startDate.Value.Date.Year}";
}
var scanResults = _elasticClient.Search<OemCatalogModel>(s => s.Index(indexName)
.Source(sf => sf
.Includes(i => i
.Fields(
f => f.Event,
f => f.MemberId,
f => f.IsInternalUser,
f => BuildTermQuery<OemCatalogModel, string>(c => c.IndexName, indexName),
f => f.IsMobile,
f => f.VinNumber,
f => f.Timestamp
)
)
)
.Query(q => q.Range(p =>
p.Field(f => f.Timestamp)
.GreaterThanOrEquals(startDate.Value.ToUnixTimeSeconds())
.LessThanOrEquals(endDate.Value.ToUnixTimeSeconds())
))
.Query(q => q.Match(m => m
.Field(f => f.Event)
.Query(eventName)
)).Size(10000).Scroll("10s"));
var scrolls = 0;
var results = this._elasticClient.Scroll<OemCatalogModel>("4s", scanResults.ScrollId);
while (results.Documents.Any())
{
yield return results.Documents;
results = _elasticClient.Scroll<OemCatalogModel>("4s", results.ScrollId);
scrolls++;
}
the BuildTermQuery loooks following
private static TermQuery BuildTermQuery<T, TProp>(Expression<Func<T, TProp>> fieldExpression, string value)
{
var query = new TermQuery
{
Field = new Field(fieldExpression),
Value = value
};
return query;
}
How set default value?

LINQ: Group items, then select Min and finally select Max

I have below list of items:
ID Date
01200 11/11/2020
01200 11/11/2021
02100 01/01/2019
02100 01/01/2029
I am trying to group items by ID, then for each group select the item which has the Max date, and finally select the item which has the Min date. Taken into account above set of items, the final result would be 11/11/2021.
So I have implemented two ways here to do the same thing. Option 1 is working but option 2 isn't.
step-by-step:
// first get the groups:
var groups = items.GroupBy(i => i.ID);
// find the max date in each group
var maxDates = groups.Select(g => g.OrderByDescending(i => i.Date).First());
// now find the earliest max date
var minDate = maxDates.OrderBy(i => i.Date).First();
combined into one line:
var minDate = items.GroupBy(i => i.ID)
.Select(g => g.OrderByDescending(i => i.Date).First())
.OrderBy(i => i.Date).First();
...
GroupBy(p => p.id,
p => p.date,
(key, g) => new { id = key, date = g.Max() })
returns an IEnumerable of an anonymous type. You cannot convert anonymous type to type Foo via .ToList<Foo>.
You should rewrite you code to resolve compilation error as
var res2 = cv.GroupBy(
p => p.id,
p => p.date,
(key, g) => new Foo{ id = key, date = g.Max() //add here class name Foo
}).Aggregate((u,v) => u.date < v.date ? u: v);
EDIT: or if you not use Dump() to show result then you may use anonymous type in GroupBy() like:
var res2 = cv.GroupBy(
p => p.id,
p => p.date,
(key, g) => new { id = key, date = g.Max() }).Aggregate((u,v) => u.date < v.date ? u: v);
Also you may use #D Stanley idea to find Foo object like:
var minDate = cv.GroupBy(i => i.id,
p => p.date,
(key, g) => new Foo() { id = key, date = g.Max() }).OrderBy(e=>e.date).First();

Ranking Search Results With LINQ/.NET MVC

I have a task where I need to rank the search results based on which column the search term was found.
So for example, if the search term is found in column A of table 1, it ranks higher than if it was found in column A of table 2.
Right now, I have a linq query that joins multiple tables and searches for the search term in certain columns. I.E.
var results = db.People
.Join(db.Menu, p => p.ID, m => m.PersonID, (p, m) => new { p = p, m = m })
.Join(db.Domain, m => m.m.DomainID, d => d.ID, (m, d) => new { m = m, d = d })
.Where(d => searchTermArray.Any(x => d.m.p.p.Name.Contains(x)) || searchTermArray.Any(x => d.m.p.p.Biography.Contains(x)) || searchTermArray.Any(x => d.d.domain.Contains(x)))
.Select(p => p).Distinct();
So if the search term is found in db.People, column Name, that row/Person will rank higher than if found in db.People, column Biography, which will rank higher than if found in db.Domain, column domain.
This will order your result by the "rank". You can manipulate the query further if you also want to return the rank and not only the aggregate:
var results = db.People
.Join(db.Menu, p => p.ID, m => m.PersonID, (p, m) => new { p = p, m = m })
.Join(db.Domain, m => m.m.DomainID, d => d.ID, (m, d) => new { m = m, d = d })
.Select(d => new
{
rank = searchTermArray.Any(x => d.m.p.p.Name.Contains(x)) ? 3 : searchTermArray.Any(x => d.m.p.p.Biography.Contains(x)) ? 2 : searchTermArray.Any(x => d.d.domain.Contains(x)) ? 1 : 0,
m = d
})
.Where(a => a.rank > 0)
.OrderByDescending(a => a.rank)
.Select(a => a.m).Distinct();
Note: I take no responsibility for poor performance, that's LINQ after all.

LINQ query to dictionary

I'm trying to convert below LINQ query result into dictionary
var browser = (from tbf in context.tblFeedBacks
where tbf.email == dboard.userEmail
select tbf).GroupBy(l => l.browser)
.Select(g => new
{
browser = g.Key,
count = g.Select(l => l.browser).Distinct().Count()
});
It gives me a compilation error.
var browser = (from tbf in context.tblFeedBacks
where tbf.email == dboard.userEmail
select tbf).GroupBy(l => l.browser)
.Select(g => new
{
browser = g.Key,
count = g.Select(l => l.browser).Distinct().Count()
}).ToDictionary<string, double>(x => x.browser,y=>y.count);
Instance argument: cannot convert from
'System.Linq.IQueryable' to
'System.Linq.ParallelQuery'
got it working.
var browser = (from tbf in context.tblFeedBacks
where tbf.email == dboard.userEmail
select tbf).GroupBy(l => l.browser)
.Select(g => new
{
browser = g.Key,
count = g.Select(l => l.browser).Count()
}).ToDictionary(x => x.browser, x => x.count);

Linq orderby, can't work out how to use it

I have this function:
/// <summary>
/// Return array of all badges for a users
/// </summary>
public static Badge[] getUserBadges(int UserID)
{
Badge[] ReturnBadges;
using (MainContext db = new MainContext())
{
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c });
ReturnBadges = new Badge[q.Count()];
int i = 0;
foreach (var UserBadge in q)
{
ReturnBadges[i] = new Badge(UserBadge.TheBadge.Key);
ReturnBadges[i].Quantity = UserBadge.BadgeCount;
i++;
}
}
return ReturnBadges;
}
I wish to order by tblBadges.OrderID ascending but I can't seem to find out where to put it, can anyone help?
I've tried:
.OrderBy(c=> c.TheBadge.OrderID)
But it's not valid code. TheBadge.Key in the loop is a tblBadges type. It's confusing me a bit why intellisense wont let me do the order by anywhere!
TheBadge isn't a single badge, it's a group of badges... so I'd personally rename it if I were you. Now, which OrderId do you want to get? You've got multiple entities in the gruop. For example, you could do this:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c })
.OrderBy(x => x.TheBadge.First().OrderId);
That will order by some notional "first" element - although I don't know what the generated SQL will look like.
If you expect the OrderId to be the same for every badge with the same ID, you might use:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => new { c.BadgeID, c.OrderID })
.OrderBy(group => group.Key.OrderID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c });
Try this:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c.Key }) // *mod
.OrderBy(c=> c.TheBadge.OrderID); // * added
In the following line, TheBadge is a linq collection, not the badge itself. You want c.Key.
.Select(c => new { BadgeCount = c.Count(), TheBadge = c })

Categories

Resources