This is what my classes look like:
public class TestA : MongoDocument
{
public string ProjectID { get; set; }
public TestB Content { get; set; }
}
public class TestB
{
public string Id { get; set; }
public List<TestC> CustInfo { get; set; }
}
public class TestC
{
public string Id { get; set; }
public CustomerComment CustomerComment { get; set; }
}
public class CustomerComment
{
public string Id { get; set; }
public string notes { get; set; }
}
I would like to return only the CustomerComment based on TestA.ProjectID && TestC.Id
var projection = Builders<TestA>.Projection
.Include(x => x.Content.CustInfo);
var options = new FindOptions<TestA>
{
Projection = projection
};
var find = await Collection.FindAsync(p => p.ProjectID == "555" &&
p.Content.CustInfo.Any(l => l.Id == "123"), options).ConfigureAwait(false);
var result = await find.ToListAsync().ConfigureAwait(false);
This works but it will return everything. In this case, I would only want to return CustomerComments.
So for me to only retrieve the customerComments, I do another query based on the results from mongo. The code below gives me the correct data but I would rather do the filtering through the database.
var test = result.SelectMany(x => x.Content.CustInfo.Where(l => l.Id == "123").Select(y => y.CustomerComment)).ToList();
I think Aggregation Query meets your requirements for querying the data in the database and returning desired output.
$match - Filtering data.
$unwind - Deconstruct array fields to multiple documents.
$match - Filtering data for Content.CustInfo.
$replaceWith - Replace the current document with the new document for output.
db.collection.aggregate([
{
$match: {
"ProjectID": "555",
"Content.CustInfo.Id": "123"
}
},
{
$unwind: "$Content.CustInfo"
},
{
$match: {
"Content.CustInfo.Id": {
$eq: "123"
}
}
},
{
"$replaceWith": "$Content.CustInfo.CustomerComment"
}
])
Sample Mongo Playground
Solution 1: With AggregateFluent
Pre-requisite: Create unwind classes.
public class UnwindTestA
{
public ObjectId _id { get; set; }
public string ProjectID { get; set; }
public UnwindTestB Content { get; set; }
}
public class UnwindTestB
{
public string Id { get; set; }
public TestC CustInfo { get; set; }
}
var result = await Collection.Aggregate()
.Match(
p => p.ProjectID == "555"
&& p.Content.CustInfo.Any(l => l.Id == "123")
)
.Unwind<TestA, UnwindTestA>(x => x.Content.CustInfo)
.Match<UnwindTestA>(x => x.Content.CustInfo.Id == "123")
.ReplaceWith<CustomerComment>("$Content.CustInfo.CustomerComment")
.ToListAsync();
Solution 2: With BsonDocument
Sometimes, writing a query with AggregateFluent is quite complex. You can use the MongoDB query which is converted to BsonDocument.
BsonDocument[] aggregate = new BsonDocument[]
{
new BsonDocument("$match",
new BsonDocument
{
{ "ProjectID", "555" },
{ "Content.CustInfo.Id", "123" }
}),
new BsonDocument("$unwind", "$Content.CustInfo"),
new BsonDocument("$match",
new BsonDocument("Content.CustInfo.Id",
new BsonDocument("$eq", "123"))),
new BsonDocument("$replaceWith", "$Content.CustInfo.CustomerComment")
};
var result = await Collection.Aggregate<CustomerComment>(aggregate)
.ToListAsync();
Output
Related
Please refer to the JSON below : -
{
"operations": [
{
"creationTime": "2022-06-02T10:28:28.765+03:00",
"deviceId": "43432103",
"deviceName": "P25-SC-0228",
"id": "121985460",
"status": "PENDING",
"com_cumulocity_model": {
"op": "s",
"param": "waterStartDateTime",
"value": "1/2/2018, 7:30:00 AM"
},
"description": "Generate Plan"
},
{
"creationTime": "2022-06-02T10:28:36.276+03:00",
"deviceId": "43432103",
"deviceName": "P25-SC-0228",
"id": "121985465",
"status": "PENDING",
"com_cumulocity_model": {
"Mode": 0,
"StopStationPayload": "[{\"ControllerAddress\":11,\"StationAddress\":26}]"
},
"description": "Stop Station"
}
],
"statistics": {
"currentPage": 1,
"pageSize": 5
}
}
Please find my code below : -
namespace handleDeviceOperations
{
class Program
{
string operationID = String.Empty;
static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var services = serviceCollection.BuildServiceProvider();
var httpClientFactory = services.GetRequiredService<IHttpClientFactory>();
var httpClientGetOperations = httpClientFactory.CreateClient("getOperations");
var request1 = await httpClientGetOperations.GetAsync("");
if (request1.IsSuccessStatusCode)
{
var responseMessage1 = await request1.Content.ReadAsStringAsync();
JObject obj = JObject.Parse(responseMessage1);
var root = JsonConvert.DeserializeObject<RootObject>(responseMessage1);
RootObject myDeserializedObject = JsonConvert.DeserializeObject<RootObject>(responseMessage1);
if (obj["operations"].HasValues)
{
foreach(var item in myDeserializedObject.operations)
{
switch(item.description)
{
case "Generate Plan":
var gen_plan=JObject.Parse(responseMessage1)["operations"];
string[] gen_plan_list_operationID =gen_plan.Select(o => (string) o["id"]).ToArray();
JObject[] gen_plan_list_payload = gen_plan.Select(o => (JObject) o["com_cumulocity_model"]).ToArray();
break;
case "Stop Station":
var stop_st=JObject.Parse(responseMessage1)["operations"];
string[] stop_st_list_operationID =stop_st.Select(o => (string) o["id"]).ToArray();
JObject[] stop_st_list_payload = stop_st.Select(o => (JObject) o["com_cumulocity_model"]).ToArray();
var httpClientStopStation = httpClientFactory.CreateClient("executeOperations");
var request4 = await httpClientStopStation.PostAsync("");
break;
}
}
}
}
}
private static void ConfigureServices(ServiceCollection services)
{
services.AddHttpClient("getOperations", options =>
{
options.BaseAddress = new Uri("https://myurl.com?deviceId=43432103");
options.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic","auth value");
});
services.AddHttpClient("executeOperations", options =>
{
options.BaseAddress = new Uri("https://myurl.com/"+operationID);
options.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic","auth value");
options.DefaultRequestHeaders.Add("Accept", "application/vnd.com.nsn.xyz.operation+json");
});
}
public class RootObject
{
public List<operation> operations { get; set; }
}
public class operation
{
public golfController com_cumulocity_model { get; set; }
public string description {get; set;}
}
public class golfController
{
public int mode { get; set; }
public string StopStationPayload { get; set; }
}
}
}
Question #1
In the switch case I want to fetch the value of com_cumulocity_model and id which belongs to the same JSON Object where case(value_of_description) is satisfied.
For example :
If case "Stop Station": is satisfied, I want to fetch the equivalent value of com_cumulocity_model and id inside it i.e. {"Mode": 0,"StopStationPayload": "[{\"ControllerAddress\":11,\"StationAddress\":26}]"} and "121985465" respectively. It must be compared to the value inside case and fetched on based of that.
Question #2
How do we add this value of id = "121985465" which we discussed above to the end of th url for making PostAsync request inside case("Stop Station") in lines var httpClientStopStation = httpClientFactory.CreateClient("executeOperations"); var request4 = await httpClientStopStation.PostAsync("");?
Short way. If you need just com_cumulocity_model
var operations = JObject.Parse(json)["operations"];
var com_cumulocity_model = operations.Where(o => (string) o["description"] == "Stop Station")
.Select(o => o["com_cumulocity_model"])
.FirstOrDefault();
Console.WriteLine(com_cumulocity_model.ToString());
result
{
"Mode": 0,
"StopStationPayload": "[{\"ControllerAddress\":11,\"StationAddress\":26}]"
}
But if you need the whole data you can use this code for deserializing json.
var data = JsonConvert.DeserializeObject<Data>(json);
classes
public class Data
{
public List<Operation> operations { get; set; }
public Statistics statistics { get; set; }
}
public class Operation
{
public DateTime creationTime { get; set; }
public string deviceId { get; set; }
public string deviceName { get; set; }
public string status { get; set; }
public ComCumulocityModel com_cumulocity_model { get; set; }
public string description { get; set; }
}
public class ComCumulocityModel
{
public string op { get; set; }
public string param { get; set; }
public string value { get; set; }
public int? Mode { get; set; }
public string StopStationPayload { get; set; }
}
public class Statistics
{
public int currentPage { get; set; }
public int pageSize { get; set; }
}
you can just remove Statistics class and statitics property from Data if you dont need it. The same about another properties
Now you can use Linq to get any data, for example
ComCumulocityModel com_cumulocity_model = data.operations
.Where(o => o.description == "Stop Station")
.Select(o => o.com_cumulocity_model)
.FirstOrDefault();
result (in json format)
{
"Mode": 0,
"StopStationPayload": "[{\"ControllerAddress\":11,\"StationAddress\":26}]"
}
how to print
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting=Newtonsoft.Json.Formatting.Indented
};
Console.WriteLine(JsonConvert.SerializeObject( com_cumulocity_model, jsonSettings));
Imagine I have this document structure:
DocumentOne
{
string Id { get; set; };
string InnerId { get; set; }
DocumentTwo[] InnerDocuments { get; set; }
}
DocumentTwo
{
string Id { get; set; }
string AnotherField { get; set; }
}
I try to write query to filter documents by condition DocumentOne.InnerId != DocumentTwo.Id in .net using mongodb driver.
I tried to use
Builder<DocumentOne>.Filter.ElemMatch(x => x.InnerDocuments, y => y.Id != ???)
but I cannot access InnerId in this query (question marks)
If I try to use Fluent Syntax like
database.Find(x => x.InnerDocuments.Contains(y => y.Id != x.InnerId))
or
database.Find(!x => x.InnerDocuments.Any(y => y.Id != x.InnerId))
I got error message from driver.
How I need to re-write this query?
There's 2 ways to do this, either by aggregation or using a find with a expression.
We'll take a look at aggregation, as to me it flows a bit easier.
So starting off wtih we'll have 2 models for MongoDB like you said
public class DocumentOne
{
public string Id { get; set; }
public string InnerId { get; set; }
public DocumentTwo[] InnerDocuments { get; set; }
}
public class DocumentTwo
{
public string Id { get; set; }
public string AnotherField { get; set; }
}
Then we'll need a second projection to keep track of things later down the line when we unwind the inner documents:
public class DocumentOneProjection
{
public string Id { get; set; }
public string InnerId { get; set; }
public DocumentTwo InnerDocuments { get; set; }
}
So we'll throw some data in to MongoDB to play around with
var client = new MongoClient();
var database = client.GetDatabase("test");
var collection = database.GetCollection<DocumentOne>("documents");
await database.DropCollectionAsync(collection.CollectionNamespace.CollectionName);
await collection.InsertManyAsync(new[]
{
new DocumentOne()
{
Id = "1", InnerId = "10", InnerDocuments = new[]
{
new DocumentTwo()
{
Id = "11"
}
}
},
new DocumentOne()
{
Id = "2", InnerId = "20", InnerDocuments = new[]
{
new DocumentTwo()
{
Id = "20"
}
}
},
new DocumentOne()
{
Id = "3", InnerId = "30", InnerDocuments = new[]
{
new DocumentTwo()
{
Id = "30"
},
new DocumentTwo()
{
Id = "31"
}
}
}
});
Then we'll be able to create an aggregation query
var items = await collection.Aggregate()
.Unwind(x => x.InnerDocuments)
.AppendStage<BsonDocument>(
#"{ $addFields: { matchInner: { $cmp: [ ""$InnerId"", ""$InnerDocuments._id"" ] } } }")
.Match("{ matchInner: { $ne : 0 } }") // 0 if the two values are equivalent.
.AppendStage<DocumentOneProjection>(
#"{ $unset: ""matchInner"" }")
.ToListAsync();
This query starts off with unwinding all the inner documents, this will create a document for each document in the array (https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/)
Then it creates a new field that we will then create a match on later, this uses a compare ($cmp - https://docs.mongodb.com/manual/reference/operator/aggregation/cmp/)
Then we just match the new field and then execute the query to get back the documents.
How can i make filter and add/update third, fourth level child of a mongodb document using C#.
I can add/Update till second level but not further. Please provide me a solution or any kind reference from where can get help. Is there any other way to do it except builders..Elemmatch.
Here is my classes and code:
namespace CrudWithMultilvelNestedDoc
{
public class Channel
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Episode[] Episodes { get; set; }
}
public class Episode
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Track[] Tracks { get; set; }
}
public class Track
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Like[] Likes { get; set; }
}
public class Like
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
}
}
//First Method
//var filter = Builders<Channel>.Filter.And(Builders<Channel>
// .Filter.Where(x => x.Id == "5e4606e6ae7b090688671416"), // OR &
// Builders<Channel>.Filter.ElemMatch(e => e.Episodes, Builders<Episode>
// .Filter.Eq(e => e.Id, "5e460851d29c1b3df4d27b7d")));
//Second Method
//var filter = Builders<Channel>.Filter.Eq(e => e.Id, "5e4606e6ae7b090688671416")
// & Builders<Channel>.Filter.ElemMatch(e => e.Episodes, Builders<Episode>.Filter.Eq(e => e.Id, "5e46071d385a672b0cea0f86"));
//Third Method
var filter = channelFilter.ElemMatch(e => e.Episodes, episodeFilter.ElemMatch(e=> e.Tracks, trackFilter.Eq(e => e.Id, "5e460dbe2bc5e70c9cfeac21")));
var data = collection.Find(filter);
//Update Filter
var update = Builders<Channel>.Update.Push("Episodes[-1].Tracks[-1].Likes", like);
var result = collection.UpdateOne(filter, update);
//Data
{"_id":"5e4606e6ae7b090688671416","Name":"Channel 1","Episodes":[{"_id":"5e46071d385a672b0cea0f86","Name":"Episode 1","Tracks":[{"_id":"5e460dbe2bc5e70c9cfeac21","Name":"Trak 1","Likes":[]},{"_id":"5e4612d60747a2121870c815","Name":"Trak 2","Likes":[]}]},{"_id":"5e460851d29c1b3df4d27b7d","Name":"Episode 2","Tracks":[{"_id":"5e460e307ca6843758ce814e","Name":"Trak 1","Likes":[]}]}]}
As per documentation:
The positional $ operator cannot be used for queries which traverse more than one array
So using -1 is not a way forward here. The approach you should take is the $ positional filtered operator.
There's no strongly-typed representation in C# so your code can look like below:
var filter = Builders<Channel>.Filter.Eq(x => x.Id, "5e4606e6ae7b090688671416");
var like = new Like() {Name = "new like", Id = "1"};
var episodeId = "5e46071d385a672b0cea0f86";
var trackId = "5e460dbe2bc5e70c9cfeac21";
var update = Builders<Channel>.Update.Push("Episodes.$[e].Tracks.$[t].Likes", like);
var arrayFilters = new List<ArrayFilterDefinition>();
ArrayFilterDefinition<BsonDocument> episodesFilter = new BsonDocument("e._id", new BsonDocument("$eq", episodeId));
ArrayFilterDefinition<BsonDocument> tracksFilter = new BsonDocument("t._id", new BsonDocument("$eq", trackId));
arrayFilters.Add(episodesFilter);
arrayFilters.Add(tracksFilter);
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var result = mongoDBCollection.UpdateOne(filter, update, updateOptions);
I'm indexing a type that has percolate query but Nest/elasticsearch choose to ignore the query property.
public class MyQueryModel
{
public string Id { get; set; }
public string UserId { get; set;}
public string Email { get ; set;}
public string Name { get; set; }
public string State { get; set; }
public QueryContainer PercolatedQuery { get; set; }
}
public class DocModel
{
public string Id { get; set; }
public string Title { get; set; }
public string State { get; set; }
public string Category { get; set;}
public string Email { get; set; }
}
EDIT: some of property names between the 2 are same by coincidence. They totally mean different things on either of the 2 models and maybe mapped differently.
my mappings:
on queries index:
client.CreateIndex("on_my_queries", c => c
.Mappings(m => m
.Map<MyQueryModel>(mq => mq
.AutoMap()
.Properties(props => props
.Percolator(perc => perc
.Name(m => m.PercolatedQuery)
)
)
)
)
)
on doc index
client.CreateIndex("on_my_docs", c => c
.Mappings(m => m
.Map<MyDocModel>(md => md
.AutoMap()
)
)
)
Indexing my query model:
var queryModel = new MyQueryModel
{
Id = "some-id",
UserId = "some-user-id",
Email = "some-valid-email",
State = "some-valid-state",
PercolatedQuery = new TermQuery
{
Field = "category",
Value = "some-valid-cat-on-my-doc-models"
}
}
var request = new IndexRequest<QueryModel>(DocumentPath<MyQueryModel>.Id(queryModel));
var result = client.Index(request);
Everything gets indexed except the PercolatedQuery field. After scratching a lot of my head, I find out that client is not even serializing it. I ran the following only to see that PercolatedQuery was not serialized:
var jsonString = client.Serializer.SerializeToString(request);
jsonString:
{
"id" : "some-id",
"userId" : "some-user-id",
"email: : "some-valid-email",
"state" : "some-valid-state"
}
What client see as percolated query:
var queryString = client.Serializer.SerializeToString(queryModel.PercolatedQuery);
queryString:
{
"term": {
"category": {
"value": "some-valid-cat-on-my-doc-models"
}
}
}
I hope it's more clear what I want to do from the code than the title. Basically I am grouping by 2 fields and want to reduce the results into a collection all the ProductKey's constructed in the Map phase.
public class BlockResult
{
public Client.Names ClientName;
public string Block;
public IEnumerable<ProductKey> ProductKeys;
}
public Block()
{
Map = products =>
from product in products
where product.Details.Block != null
select new
{
product.ClientName,
product.Details.Block,
ProductKeys = new List<ProductKey>(new ProductKey[]{
new ProductKey{
Id = product.Id,
Url = product.Url
}
})
};
Reduce = results =>
from result in results
group result by new {result.ClientName, result.Block} into g
select new BlockResult
{
ClientName = g.Key.ClientName,
Block = g.Key.Block,
ProductKeys = g.SelectMany(x=> x.ProductKeys)
};
}
I get some weird System.InvalidOperationException and a source code dump where basically it is trying to initialize the list with an int (?).
If I try replacing the ProductKey with just IEnumerable ProductIds (and make appropriate changes in the code). Then the code runs but I don't get any results in the reduce.
You probably don't want to do this. Are you really going to need to query in this manner? If you know the context, then you should probably just do this:
var q = session.Query<Product>()
.Where(x => x.ClientName == "Joe" && x.Details.Block == "A");
But, to answer your original question, the following index will work:
public class Products_GroupedByClientNameAndBlock : AbstractIndexCreationTask<Product, Products_GroupedByClientNameAndBlock.Result>
{
public class Result
{
public string ClientName { get; set; }
public string Block { get; set; }
public IList<ProductKey> ProductKeys { get; set; }
}
public class ProductKey
{
public string Id { get; set; }
public string Url { get; set; }
}
public Products_GroupedByClientNameAndBlock()
{
Map = products =>
from product in products
where product.Details.Block != null
select new {
product.ClientName,
product.Details.Block,
ProductKeys = new[] { new { product.Id, product.Url } }
};
Reduce = results =>
from result in results
group result by new { result.ClientName, result.Block }
into g
select new {
g.Key.ClientName,
g.Key.Block,
ProductKeys = g.SelectMany(x => x.ProductKeys)
};
}
}
When replicating I get the same InvalidOperationException, stating that it doesn't understand the index definition (stack trace omitted for brevity).
Url: "/indexes/Keys/ByNameAndBlock"
System.InvalidOperationException: Could not understand query:
I'm still not entirely sure what you're attempting here, so this may not be quite what you're after, but I managed to get the following working. In short, Map/Reduce deals in anonymous objects, so strongly typing to your custom types makes no sense to Raven.
public class Keys_ByNameAndBlock : AbstractIndexCreationTask<Product, BlockResult>
{
public Keys_ByNameAndBlock()
{
Map = products =>
from product in products
where product.Block != null
select new
{
product.Name,
product.Block,
ProductIds = product.ProductKeys.Select(x => x.Id)
};
Reduce = results =>
from result in results
group result by new {result.Name, result.Block}
into g
select new
{
g.Key.Name,
g.Key.Block,
ProductIds = g.SelectMany(x => x.ProductIds)
};
}
}
public class Product
{
public Product()
{
ProductKeys = new List<ProductKey>();
}
public int ProductId { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public string Block { get; set; }
public IEnumerable<ProductKey> ProductKeys { get; set; }
}
public class ProductKey
{
public int Id { get; set; }
public string Url { get; set; }
}
public class BlockResult
{
public string Name { get; set; }
public string Block { get; set; }
public int[] ProductIds { get; set; }
}