How to use Conditionals in NEST Queries Elasticsearch - c#

I want to build a NEST query for Elasticsearch depending on the user input with an If Else statement. At the moment it only accepts one condition in the must part and the other one isn't added to the query.
The following code compiles to this http request:
{"from":0,"query":{"bool":{"must":[{"nested":{"path":"customer","query":{"term":{"customer.customerId":{"value":1}}}}}]}},"size":10}
as you can see the SearchPersonId isn't added into the must condition.
The search method:
private ISearchResponse<Country> GetFilteredResults(ElasticClient client, Query query)
{
var searchRequest = new SearchDescriptor<Country>();
searchRequest.From(0).Size(10).Query(q =>
{
q.Bool(b => b
.Must(mc =>
{
if (query.CustomerId != 0) mc.SearchCustomerId(query.CustomerId);
if (query.PersonId != 0) mc.SearchPersonId(query.PersonId);
return mc;
})
);
return q;
});
return client.Search<Country>(searchRequest);
}
The query methods:
public static class Helpers
{
public static QueryContainer SearchPersonId(this QueryContainerDescriptor<Country> container, string personId)
{
return container
.Nested(n => n
.Path(p => p.Person)
.Query(q => q
.Term(t => t
.Field(f => f.Person.PersonId).Value(personId))));
}
public static QueryContainer SearchCustomerId(this QueryContainerDescriptor<Country> container, string customerId)
{
return container
.Nested(n => n
.Path(p => p.Customer)
.Query(q => q
.Term(t => t
.Field(f => f.Customer.CustomerId).Value(customerId))));
}
}

One of the overloads of Must method accepts array of QueryContainer which can help you implement conditional logic
ISearchResponse<Country> GetFilteredResults(ElasticClient client, Query query)
{
var queryContainers = new List<QueryContainer>();
var descriptor = new QueryContainerDescriptor<Country>();
if (query.CustomerId != 0) queryContainers.Add(descriptor.SearchCustomerId(query.CustomerId));
if (query.PersonId != 0) queryContainers.Add(descriptor.SearchPersonId(query.PersonId));
var searchRequest = new SearchDescriptor<Country>();
searchRequest.From(0).Size(10).Query(q => q.Bool(b => b.Must(queryContainers.ToArray())));
return client.Search<Country>(searchRequest);
}

Related

JsonResults with DbSet

I was wondering if there was a way to combine these two. There are two multiple db sets. I've already tried putting with the same variable. Any ideas?
public JsonResult GetProductByPDLN(int pdlnId, int copcCode)
{
_context.Configuration.ProxyCreationEnabled = false;
var prod = _context.ProductLines
.Where(pl => pl.Id == pdlnId)
.Select(p => p.Products)
.ToList();
var copc = _context.ProfitCenters
.Where(c => c.Id == copcCode)
.Select(p => p.ProductLines)
.ToList();
return Json(prod && copc, JsonRequestBehavior.AllowGet);
}
Either create a new class or an anonymous. Something along these lines:
public JsonResult GetProductByPDLN(int pdlnId, int copcCode)
{
_context.Configuration.ProxyCreationEnabled = false;
var prod = _context.ProductLines
.Where(pl => pl.Id == pdlnId)
.Select(p => p.Products)
.ToList();
var copc = _context.ProfitCenters
.Where(c => c.Id == copcCode)
.Select(p => p.ProductLines)
.ToList();
return Json(new {ProductLines = prod, ProfitCenters = copc}, JsonRequestBehavior.AllowGet);
}

How to iterate fields as per coming input fields in NEST queries?

I written my NEST query like below
var searchResponse = await _elasticClient.SearchAsync<T>(s => s
.Index(indexName)
.Query(q => q
.Bool(b => b
.Should(
sh => sh.Prefix(pr => pr.Field(fieldNames[0]).Value(fieldValues[0])),
sh => sh.Prefix(pr => pr.Field(fieldNames[1]).Value(fieldValues[1]))
)
))
.Aggregations(ag=>ag.Cardinality(sumName,ca=>ca.Field(cardinalField)))
.Collapse(co=>co.Field(cardinalField))
).ConfigureAwait(false);
return searchResponse.Count;
}
I need to iterate below code
sh => sh.Prefix(pr => pr.Field(fieldNames[0]).Value(fieldValues[0])),
sh => sh.Prefix(pr => pr.Field(fieldNames[1]).Value(fieldValues[1]))
as per input array
I tried like below
private static QueryContainer MatchAny<T>(QueryContainerDescriptor<T> descriptor, Field[] fields, string value) where T : class
{
QueryContainer q = new QueryContainer();
for (int i=0;i<=fields.Length-1;i++)
{
q |= descriptor.Match(t => t.Field(fields[i]).Query(value));
}
return q;
}
var searchResponse = await _elasticClient.SearchAsync<T>(s => s
.Index(indexName)
.Query(q => q
.Bool(b => b
.Should(sh=> MatchAny(sh, fieldNames, fieldValue)
)
))
).ConfigureAwait(false);
but getting compile error :
cannot convert from 'string[]' to 'Nest.Field[]'
so how to iterate fields as per coming no of inputs in NEST queries ?

How to use the Exact array value instead of Contains [duplicate]

This question already has answers here:
LINQ equal instead of Contains
(3 answers)
Closed 5 years ago.
I need to use Equals method or something similar instead of using Contains method because i want to search in database for the exact values in selectedDeviceTypeIDs array not any of it.
IEnumerable<Guid> selectedDeviceTypeIDs = DeviceTypeIDs
.Split(',')
.Select( Guid.Parse )
.AsEnumerable();
query = query
.Where( j =>
j.HospitalDepartments.Any( jj =>
jj.Units.Any( m =>
m.Devices.Any( w =>
selectedDeviceTypeIDs.Contains( w.DeviceTypeID )
)
)
)
);
Here is my full code
public HttpResponseMessage GetAvailableHospitalsByAjax(System.Guid? DirectorateOfHealthID = null, System.Guid? UnitTypeID = null, string DeviceTypeIDs = null)
{
Context db = new Context();
var query = db.Hospitals.AsQueryable();
if (DeviceTypeIDs != null)
{
IEnumerable<Guid> selectedDeviceTypeIDs = DeviceTypeIDs.Split(',').Select(Guid.Parse).AsEnumerable();
query = query.Where(j => j.HospitalDepartments.Any(jj => jj.Units.Any(m => m.Devices.Any(w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID)))));
}
if (UnitTypeID != null)
{
query = query.Where(j => j.HospitalDepartments.Any(www => www.Units.Any(u => u.UnitTypeID == UnitTypeID)));
}
if (DirectorateOfHealthID != null)
{
query = query.Where(h => h.DirectorateHealthID == DirectorateOfHealthID);
}
query = query.Where(j => j.HospitalDepartments.Any(u => u.Units.Any(d => d.Devices.Any(s => s.Status == Enums.DeviceStatus.Free)))
&& j.HospitalDepartments.Any(hd => hd.Units.Any(u => u.Beds.Any(b => b.Status == Enums.BedStatus.Free))));
var list = query.ToList();
return Request.CreateResponse(HttpStatusCode.OK, list);
}
Your problem is not Contains() but with the Any() method used in your query which will return true immediately after it finds a device whose DeviceTypeID is in the provided selectedDeviceTypeIDs list.
If you need to check if all the devices of a unit match all the items in the list, you could use:
query = query
.Where(j =>
j.HospitalDepartments.Any(jj =>
jj.Units.Any(m =>
m.Devices.All(
w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID))
&&
selectedDeviceTypeIDs.All(
g => m.Devices.Select(d => d.DeviceTypeID).Contains(g))
)
)
);
Note that if you have duplicate items in the selectedDeviceTypeIDs but not in the Devices of the Unit, it will still return true.

Generic method with multiple parameter

I am trying to write a Generic method for the method CheckResult so that it could be used across different classes. For example, if I have a generic method then the only thing that is going to change is the classname. Here it is ClassA , another method could pass classB.
public bool CheckResult(Guid Id, List<ClassA> model,List<ClassA> existingEntities)
{
var ids = existingEntities?.Select(x => x.Id).Except(model.Select(x => x.Id)).ToList();
var check = existingEntities?.Where(o => ids.Any(c => c == o.Id && o.EffectiveTo >= DateTime.Today)).ToList();
check?.AddRange(model);
var dateModel = check.Select(x => new TimeInterval(x.EffectiveFrom, x.EffectiveTo)).ToList();
return true;
}
--------------This is what I was attempting to do---------------------
public static bool OpTest<T>(T model, T existingEntities, Guid t) where T : class
{
// var existingEntities = smRepository.GetStationMapping(t, StatusEnum.ALL); //smRepository.GetStationMapping(t, StatusEnum.ALL);
var ids = existingEntities?.Select(x => x.Id).Except(model.Select(x => x.Id)).ToList();
var check = existingEntities?.Where(o => ids.Any(c => c == o.Id && o.EffectiveTo >= DateTime.Today)).ToList();
check?.AddRange(model);
var dateModel = check.Select(x => new TimeInterval(x.EffectiveFrom, x.EffectiveTo)).ToList();
return true;
}
This is my first time writing generic any help is appreciated. The code is erroring out ?
I see a problem here. You're trying to access members on objects of type T, but T is an unknown type; it's not guaranteed to have the members you're trying to access that. C# doesn't like that.
I see two options here:
The robust option
Create an interface, IMyInterface, with the appropriate members. Then write this:
public static bool OpTest(IEnumerable<IMyInterface> model, IEnumerable<IMyInterface> existingEntities, Guid t)
{
List<SomeType> ids = existingEntities?.Select(x => x.Id).Except(model.Select(x => x.Id)).ToList();
List<IMyInterface> check = existingEntities?.Where(o => ids.Any(c => c == o.Id && o.EffectiveTo >= DateTime.Today)).ToList();
check?.AddRange(model);
List<TimeInterval> dateModel = check.Select(x => new TimeInterval(x.EffectiveFrom, x.EffectiveTo)).ToList();
return true;
}
The quick and dirty option
Use dynamic instead. Note that if you use this, your code will break at runtime if you make any mistakes. If you don't want it to break like that, then don't use this option.
public static bool OpTest(IEnumerable<dynamic> model, IEnumerable<dynamic> existingEntities, Guid t)
{
List<dynamic> ids = existingEntities?.Select(x => x.Id).Except(model.Select(x => x.Id)).ToList();
List<dynamic> check = existingEntities?.Where(o => ids.Any(c => c == o.Id && o.EffectiveTo >= DateTime.Today)).ToList();
check?.AddRange(model);
List<dynamic> dateModel = check.Select(x => new TimeInterval(x.EffectiveFrom, x.EffectiveTo)).ToList();
return true;
}
public static bool OpTest<T>(List<T> model, List<T> existingEntities, Guid t) where T : class
{
// var existingEntities = smRepository.GetStationMapping(t, StatusEnum.ALL); //smRepository.GetStationMapping(t, StatusEnum.ALL);
var ids = existingEntities?.Select(x => x.Id).Except(model.Select(x => x.Id)).ToList();
var check = existingEntities?.Where(o => ids.Any(c => c == o.Id && o.EffectiveTo >= DateTime.Today)).ToList();
check?.AddRange(model);
var dateModel = check.Select(x => new TimeInterval(x.EffectiveFrom, x.EffectiveTo)).ToList();
return true;
}
keep your items as list or you cant use linq on it.

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