cosmosdb where clause in sub lists with linq - c#

My root data has several sub collections, and I need to do a where clause on 2 of them, sample:
{
"id": "000092224369_0030",
....
"orderData": {
...
"request_secondary": {
"_type": "request_secondary",
"secondary_requests": [{
"secondary_description":"do something" }]
},
"partnership": {
"contacts": [
{
"role": "AG",
"first_name": "LIEBENS Eric",
"last_name": null,
"_type": "contact",
"email": "eric.liebens#gmail.com",
"tel1": "0495-543905",
"tel2": null,
"vat": null
},
{
"role": "ZO",
"first_name": "Coralie",
"last_name": "Demeyere",
"_type": "contact",
"email": "coralie.demeyere#ores.net",
"tel1": "069/256533",
"tel2": null,
"vat": null
},
{
"role": "ZR",
"first_name": "GASBARRO Gianni",
"last_name": null,
"_type": "contact",
"email": null,
"tel1": "0495-385479-0",
"tel2": null,
"vat": "BE0474281005"
}
],
...
Here I need to do a query that bring back the record where any secondary_description equals a text, or a contact with a name with that text.
It should translate to sql for in something this:
SELECT c.id from c
join x in c.orderData.request_secondary
join y in c.orderData.partnership.contacts
where x.secondary_description ='sometin' or y.first_name= 'sometin'
I have tried this solution:
How to query sub document (list type) in Cosmos Db
It works great with one sub collection, but I have no idea how i could get this to work with several selectmany...
Is there any way I can do this in linq?
Thanks!

Based on your description, i think your SQL needs to be tweaked a little bit.
SELECT c.id from c
join x in c.orderData.request_secondary.secondary_requests
join y in c.orderData.partnership.contacts
where x.secondary_description ='something' or y.first_name= 'something'
However, there's going to be duplicate data in the results.So , I also suggestion you adopt the stored procedure which I answered in the thread:How to query sub document (list type) in Cosmos Db.
function sample() {
var collection = getContext().getCollection();
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT * FROM root r',
function (err, feed, options) {
if (err) throw err;
if (!feed || !feed.length) getContext().getResponse().setBody('no docs found');
else {
var returnResult = [];
for(var i = 0;i<feed.length;i++){
var isContinue = true;
var array1 = feed[i].orderData.request_secondary.secondary_requests;
var array2 = feed[i].orderData.partnership.contacts;
for(var j = 0;i<array1.length;j++){
if(array1[j].secondary_description == 'something'){
returnResult.push(feed[i]);
isContinue=false;
break;
}
}
if(isContinue){
for(var k = 0;i<array2.length;k++){
if(array2[j].first_name == 'something'){
returnResult.push(feed[i]);
break;
}
}
}
}
getContext().getResponse().setBody(returnResult);
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
Update Answer:
You could build LINQ from SQL follow the doc.
client.CreateDocumentQuery().SelectMany((x) =>
x.orderData.requestSecondary.secondaryRequests.Where(
s=>s.secondaryDescription == "something"
) ||
x.orderData.partnership.contacts.Where(
c=>c.firstName == "something"
)
However , i think you still need to resolve the duplicate data of the result set on your client.
Hope it helps you.

Related

Ho to filter on linq queryes c# by key?

i want to filter my query using c# and EF Core so that from this list:
{
"result": [
{
"commissionId": "b99a0152-b3a5-4ff6-f19e-08da6a45d751",
"commissionCode": "0001",
"commissionDescription": "Description1",
"activityId": "323e6237-c3f6-4616-3117-08da6a28ad38",
"activityCode": "01.1",
"activityDescription": "DELETE FILE",
"minuteWorked": 10440,
"activityList": null,
"timeWorked": "7.06:00:00"
},
{
"commissionId": "b99a0152-b3a5-4ff6-f19e-08da6a45d751",
"commissionCode": "0001",
"commissionDescription": "Description1",
"activityId": "95d37329-acac-4443-3118-08da6a28ad38",
"activityCode": "01.2",
"activityDescription": "DOWNLOAD FILE",
"minuteWorked": 15,
"activityList": null,
"timeWorked": "00:15:00"
},
{
"commissionId": "b99a0152-b3a5-4ff6-f19e-08da6a45d751",
"commissionCode": "0001",
"commissionDescription": "Description1",
"activityId": "89fd1d93-b5b8-4c08-3119-08da6a28ad38",
"activityCode": "01.3",
"activityDescription": "FILE SAVE",
"minuteWorked": 0,
"activityList": null,
"timeWorked": "00:00:00"
},
I get this:
{
"result": [
{
"commissionId": "b99a0152-b3a5-4ff6-f19e-08da6a45d751",
"commissionCode": "0001",
"commissionDescription": "Description1",
"activityList": "{ 323e6237-c3f6-4616-3117-08da6a28ad38",95d37329-acac-4443-3118-
08da6a28ad38,89fd1d93-b5b8-4c08-3119-08da6a28ad38 }
"timeWorked": "7.06:00:00"
},
Just to clarify the first list is obtained doing a:
GroupBy( c => new {c.ActivityId, c.CommissionId}
And then:
Select(grp => new RegistrationStatisticViewModel() {CommissionId = grp.Key.CommissionId,
CommissionCode = grp.First().Commission.Code,
CommissionDescription = grp.First().Commission.Description,
ActivityId = grp.Key.ActivityId,
ActivityCode = grp.First().Activity.Code,
ActivityDescription = grp.First().Activity.Description,
MinuteWorked = grp.Sum(c => c.MinuteWorked)
})
.ToListAsync(),
The second list essentially groups all Commission with the same id and then i guess ? it has a property of type List<T> that stores all the activityId.
So i want to group all the activity for one commissionId but only have one commissionCode etc.
Thanks to whoever replies!
Try this:
grp
.ToList()
.Where(cm => cm.CommissionId = CommissionId)
.ToList()
.ForEach(c =>
{
grp.activityList.Add(c.activityId);
});
NOTE: untested code.

System.Linq.Dynamic.ParseException: 'expression expected'

I get this error when I'm doing the datatable serverside processing.
The error is thrown when I call this method with Ajax.
I think the problem is server-side coding and I didn't get the problem maybe it's a dynamic linq syntax error.
What is the right syntax for dynamic linq for this statement?
This is my C# code:
public ActionResult Indexer()
{
int start = Convert.ToInt32(Request["start"]);
int length = Convert.ToInt32(Request["length"]);
string searchValue = Request["search[value]"];
string sortColumnName = Request["columns["+Request["order[0][column]"] + "][name]"];
string sortDirection = Request["order[0][dir]"];
int recordsTotal = 0;
List<Employee> Employee = _context.Employees.ToList();
if (!string.IsNullOrEmpty(searchValue)) //filter
{
Employee = Employee.Where(x => x.Emp_ID.ToString().Contains(searchValue.ToString()) ||
x.First_Name.ToLower().Contains(searchValue.ToLower()) ||
x.Last_Name.ToLower().Contains(searchValue.ToLower()) ||
x.Gender.ToLower().Contains(searchValue.ToLower()) ||
x.Salary.ToString().Contains(searchValue.ToString())).ToList();
}
//sorting
if (!(string.IsNullOrEmpty(sortColumnName) && string.IsNullOrEmpty(sortDirection)))
{
// This line throws the error
Employee = Employee.OrderBy(sortColumnName + " " + sortDirection).ToList();
}
// Paging
Employee = Employee
.Skip(start).Take(length)
.ToList<Employee>();
recordsTotal = Employee.Count();
return Json(new { data = Employee }, JsonRequestBehavior.AllowGet);
}
And this is the script which I believe is fine:
#section scripts {
<script src="https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function () {
$('#mytable').DataTable({
"ajax": {
"url": "/Home/Indexer",
"type": "POST",
"datatype": "josn",
},
"columns": [
{ "data": "Emp_ID", "name": "Emp_Id" },
{ "data": "First_Name", "name": "First_Name" },
{ "data": "Last_Name", "name": "Last_Name" },
{ "data": "Gender", "name": "Gender" },
{ "data": "Salary", "name": "Salary" },
],
"serverSide": "true",
"order": [0, "acs"],
"processing": "true",
});
})
</script>
}
You have a couple of problems here.
First, the list
List<Employee> Employee = _context.Employees.ToList();
is a really bad idea. It's reading the entire database table into memory, which, if it's large, could cause memory issues. But, more importantly, everything done after that will be done in C# on your web server, instead of by the database server. -- You are cutting the database server out of the very thing it was designed to do.
You want to keep it as an IQueryable<> until the very end, and that would be the only place you use .ToList().
IQueryable<Employee> Employee = _context.Employees;
Next, we have the first if(), which is mostly fine, but you know searchValue is a string, so why are you continually trying to convert it to a string? Why keep converting it to lower case? And again, no ToList()
if (!string.IsNullOrEmpty(searchValue)) //filter
{
searchValue = searchValue.ToLower();
Employee = Employee.Where(x => x.Emp_ID.ToString().Contains(searchValue) ||
x.First_Name.ToLower().Contains(searchValue) ||
x.Last_Name.ToLower().Contains(searchValue) ||
x.Gender.ToLower().Contains(searchValue) ||
x.Salary.ToString().Contains(searchValue));
}
Now, we get to the line you asked about. Basically, you are trying to get LINQ to use SQL syntax. Linq wants its own. But first, you have a logic error in your if() statement. You have essentially if (!(A && B)). That's equal to if(!A || !B). What you really want is if(!A && !B).
if (!string.IsNullOrEmpty(sortColumnName) && !string.IsNullOrEmpty(sortDirection))
{
Getting the proper OrderBy statement from a string is a tricky topic, requiring reflection, nicely documented in this question:
Dynamic Order By in Linq
Finally, we actually run the query of the dataserver, which is triggered by the ToList().
// Paging
var lstEmployee = Employee
.Skip(start).Take(length)
.ToList();
recordsTotal = lstEmployee.Count();
return Json(new { data = lstEmployee }, JsonRequestBehavior.AllowGet);

Using subquery value to filter parent result but still return all child items related to parent

I have many emails storied in a database and each of those email's have a child recipient. I'm trying to write a LINQ query where I only return emails that have a recipient address LIKE some provided string but if the parent email is returned all recipients matching or not are still returned. I've built this SQL that works but my code is only filtering on the recipients and not the parent email.
What the table contains in JSON:
[
{
"email_id": "dcfe0dfc-e4ad-4fd3-b482-61ced67a19ec",
"recipient": [
{
"email_address": "someemail#someplace.com"
},
{
"email_address": "someotherAddress#example.com"
}
]
},
{
"email_id": "f53a7681-98f8-4385-b3d2-9e9af2560664",
"recipient": [
{
"email_address": "someemail#someplace.com"
},
{
"email_address": "a_newAddress#example.com"
}
]
},
{
"email_id": "f53a7681-98f8-4385-b3d2-9e9af2560664",
"recipient": [
{
"email_address": "someAddress#email.com"
},
{
"email_address": "a_newAddress#example.com"
}
]
}
]
SQL:
SELECT * FROM email e
RIGHT JOIN recipient r on e.email_id = r.email_id
WHERE r.email_id in (SELECT email_id
from recipient WHERE email_address = 'someemail#someplace.com')
Order By e.create_date desc;
C# LINQ statement
var query = _context.Email
.IncludeFilter(x => x.Recipient.Where(r => string.IsNullOrEmpty(request.RecipientAddress) ||
EF.Functions.Like(r.EmailAddress, $"%{request.RecipientAddress}%"))).ToList();
Expected Output:
[
{
"email_id": "dcfe0dfc-e4ad-4fd3-b482-61ced67a19ec",
"recipient": [
{
"email_address": "someemail#someplace.com"
},
{
"email_address": "someotherAddress#example.com"
}
]
},
{
"email_id": "f53a7681-98f8-4385-b3d2-9e9af2560664",
"recipient": [
{
"email_address": "someemail#someplace.com"
},
{
"email_address": "a_newAddress#example.com"
}
]
}
]
With the above my result returns all the emails as expected but the recipients included in each one is only the matching recipient and I expect it to return all recipients still. Any help would be appreciated.
I figured this out. It was much simpler then I was giving it credit for.
_context.email.Where(x => xstring.IsNullOrEmpty(request.RecipientAddress) ||
x.Recipients.Any(y =>
EF.Functions.Like(y.Email, $"%{request.RecipientAddress}%")))
.Include(x => x.Recipients).OrderByDescending(x => x.CreateDate).ToList();

How to mimic URI query

This may be too basic of a question for SO, but I thought I would ask anyway.
I getting my feet wet with ElasticSearch and am trying to return a single document that has an exact match to my field of interest.
I have the field "StoryText" which is mapped as type "string" and indexed as "not_analyzed".
When I search using a the basic URI query:
123.456.0.789:9200/stories/storyphrases/_search?q=StoryText:"The boy sat quietly"
I return an exact matched document as I expected with a single hit.
However, when I use the search functionality:
GET 123.456.0.789:9200/stories/storyphrases/_search
{
"query" : {
"filtered" : {
"filter" : {
"term" : {
"StoryText" : "The boy sat quietly"
}
}
}
}
}
I get multiple documents returned with many hits (i.e. "The boy sat loudly", "The boy stood quietly" etc. etc.)
Could somebody help me to understand how I need to restructure my search request to mimic the result I get using the basic query parameter?
At present I am using NEST in C# to generate my search request which looks like this
var searchresults = client.Search<stories>(p => p
.Query(q => q
.Filtered(r => r
.Filter(s => s.Term("StoryText", inputtext))
)
)
);
Thanks very much for any and all reads and or thoughts!
UPDATE: Mappings are listed below
GET /stories/storyphrases/_mappings
{
"stories": {
"mappings": {
"storyphrases": {
"dynamic": "strict",
"properties": {
"#timestamp": {
"type": "date",
"format": "date_optional_time"
},
"#version": {
"type": "string"
},
"SubjectCode": {
"type": "string"
},
"VerbCode": {
"type": "string"
},
"LocationCode": {
"type": "string"
},
"BookCode": {
"type": "string"
},
"Route": {
"type": "string"
},
"StoryText": {
"type": "string",
"index": "not_analyzed"
},
"SourceType": {
"type": "string"
},
"host": {
"type": "string"
},
"message": {
"type": "string"
},
"path": {
"type": "string"
}
}
}
}
}
Mick
Well, first off you are executing two different queries here. The first is running in a query context whilst the second is essentially a match_all query executing in a filtered context. If your objective is simply to emulate the first query but by passing a JSON body you will need something like
GET 123.456.0.789:9200/stories/storyphrases/_search
{
"query" : {
"query_string" : {
"query" : "StoryText:'The boy sat quietly'"
}
}
}
To write this simple query using Nest you would use
var searchresults = client.Search<stories>(p => p.QueryString("StoryText:" + inputtext));
or in longer form
var searchresults = client.Search<stories>(p => p
.Query(q => q
.QueryString(qs => qs
.Query("StoryText:" + inputtext)
)
)
);
These both produce the same JSON body and send it to the _search endpoint. Assuming that storyphrases is your Elasticsearch type then you may also wish to include this in your C#.
var searchresults = client.Search<stories>(p => p
.Index("stories")
.Type("storyphrases")
.Query(q => q
.QueryString(qs => qs
.Query("StoryText:" + inputtext)
)
)
);
Having said all that and looking at your filtered query it should do what you expect according to my testing. Is your field definitely not analyzed? Can you post your mapping?

Populate KendoUi Treeview with RavenDB documents

I am using MVC4 and C#.
I have a KendoUI Treeview and I'd like to populate it with data from RavenDB.
In the demo they use this:
public JsonResult Employees(int? id)
{
var dataContext = new NorthwindDataContext();
var employees = from e in dataContext.Employees
where (id.HasValue ? e.ReportsTo == id : e.ReportsTo == null)
select new {
id = e.EmployeeID,
Name = e.FirstName + " " + e.LastName,
hasChildren = e.Employees.Any()
};
return Json(employees, JsonRequestBehavior.AllowGet);
}
Notice the argument "id" - it's an int. In RavenDb document ID's are strings eg. myDco/231...
Imagine this is a standard document:
{
"CreationDate": "12/12/2012 8:07:59 PM",
"Name": "jkljklk",
"Type": "kljkljkljkljkl",
"Description": "ljkljklj",
"CreatedByUser": "ljklklkljklj",
"Deleted": false,
"Status": "NEW",
"Parent": "product/546"
}
How would I populate the treeview?
There should not be any problem to use string instead of that int. You just need to fetch the needed records depending on that string parameter passed to the Action method.
I figured this out.
I changed my RavenDB Document to include a list of all its children:
{
"CreationDate": "12/12/2012 9:33:34 PM",
"Name": "hohoho",
"Type": "hohohoh",
"Description": "hohohoh",
"CreatedByUser": "hohohoh",
"Deleted": false,
"Status": "NEW",
"Parent": "ohohohoh",
"Children": [
"item/1",
"item/2",
"item/3"
]
}
I then returned a list of my items to the View via the controller and then iterated through them, appending all the children to their correct parent nodes:
#(Html.Kendo().TreeView().Name("TreeView").DragAndDrop(true)
.Items(treeview =>
{
foreach (var myItem in Model)
{
var myItemName = myItem.Name;
var children = myItem.Children;
treeview.Add().Text(myItemName ).Expanded(false).Items(branch =>
{
if (children != null)
{
foreach (var child in children)
{
branch.Add().Text(child);
}
}
});
}
}
))
Anyway, thanks for the responses :)
Hope this helps someone else one day.

Categories