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.
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));
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
I want to update the single document in collection with the guid as filter and update value is cityType. Every guid has different citytype here i have used 3 types it may be more.
So please give a right implementation using c# code.
Models:
public class Country
{
[BsonId]
public ObjectId Id { get; set; }
public int CountryId {get; set; }
public IEnumerable<States> States { get; set; }
}
public class States
{
public Guid Guid { get; set; }
public CityType CityType { get; set; }
}
Enum CityType
{
Unknown = 0,
Rural = 1,
Urban = 2
}
Existing Collection:
{
"_id": ObjectId("6903ea4d2df0c5659334e763"),
"CountryId": 200,
"States": [
{
"Guid": "AFCC4BE7-7585-5E46-A639-52F0537895D8",
"CityType": 0,
},
{
"Guid": "208FB603-08C7-46D9-B0C0-7AF4F691A96D",
"CityType": 0,
}
}
Input:
List<States>()
{
new States()
{
Guid = "AFCC4BE7-7585-5E46-A639-52F0537895D8",
CityType = CityType.Rural
},
new States()
{
Guid = "208FB603-08C7-46D9-B0C0-7AF4F691A96D",
CityType = CityType.Urban
}
}
Expected:
{
"_id": ObjectId("6903ea4d2df0c5659334e763"),
"CountryId": 200,
"States": [
{
"Guid": "AFCC4BE7-7585-5E46-A639-52F0537895D8",
"CityType": 1,
},
{
"Guid": "208FB603-08C7-46D9-B0C0-7AF4F691A96D",
"CityType": 2,
}
}
This is the method I have tried:
public async Task<bool> UpdateType(int countryId, IEnumerable<States> states)
{
var collection = connectionFactory.GetCollection<Country>(collectionName);
var cityTypes = states.Select(x => x.CityType);
var filter = Builders<Country>.Filter.Empty;
var update = Builders<Country>.Update.Set("States.$[edit].CityType", cityTypes);
var arrayFilters = new List<ArrayFilterDefinition>();
foreach (var state in states)
{
ArrayFilterDefinition<Country> optionsFilter = new BsonDocument("state.Guid", new BsonDocument("$eq", state.Guid));
arrayFilters.Add(optionsFilter);
}
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var result = await collection.UpdateOneAsync(filter, update, updateOptions);
return result;
}
hope all details I have added here. Thanks in advance.
You don't have to loop through it:
Let's say you have a Class1 like this:
class Question : AuditableEntity {
public string Text { get; set; }
public List<string> Tags { get; set; } = new List<string>();
so you just say:
await collection.UpdateOneAsync(
someFilter,
Builders<Class1>.Update
.Set(f => f.Text, request.Question.Text)
.Set(f => f.Tags, request.Question.Tags));
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'm having an Model Class, I need to Save it in a MongoDB Collection.
My Model Class:
public Class Employee
{
public string EmpID { get; set; }
public string EmpName { get; set; }
public List<Mobile> EmpMobile { get; set; }
}
public Class Mobile
{
public string MobID { get; set; }
public string MobNumber { get; set; }
public bool IsPreferred { get; set; }
}
The Values are
Employee EmpInfo = new Employee()
{
EmpID = "100",
EmpName = "John",
EmpMobile = new List<Mobile>()
{
{ MobNumber = "55566610", IsPreferred = true },
{ MobNumber = "55566611", IsPreferred = false },
}
}
BsonDocument _employee = new BsonDocument()
{
{ "Emp_ID", EmpInfo.EmpID },
{ "Emp_Name", EmpInfo.EmpName },
{ "Emp_Mobile", new BsonArray (EmpInfo.EmpMobile.Select(m => new
{
MobID = new ObjectId(),
MobNumber = m.MobNumber,
IsPreferred = m.IsPreferred
})) }
};
var collection = _database.GetCollection<BsonDocument>("EmployeeInfo");
collection.InsertOne(_employee);
I wish to save the above EmpInfo of type Employee in a MongoDB. But I can't able to create a BsonDocument. Kindly assist me is there is anything wrong in the above code. If yes kindly assist me.
there is no need to serialize to bson document
You can use TYPED collection and just insert data
Please see attached code snipet with updated class structure
void Main()
{
// To directly connect to a single MongoDB server
// or use a connection string
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
var collectionEmpInfo = database.GetCollection<Employee>("Employee");
Employee EmpInfo = new Employee
{
EmpID = "100",
EmpName = "John",
EmpMobile = new List<Mobile>
{
new Mobile{ MobNumber = "55566610", IsPreferred = true, MobID = ObjectId.GenerateNewId() },
new Mobile{ MobNumber = "55566611", IsPreferred = false, MobID = ObjectId.GenerateNewId() },
}
};
collectionEmpInfo.InsertOne(EmpInfo);
var empList = collectionEmpInfo.Find(new BsonDocument()).ToList();
empList.Dump(); //dump is used in linqPad
}
public class Employee
{
public ObjectId Id { get; set; }
public string EmpID { get; set; }
public string EmpName { get; set; }
public List<Mobile> EmpMobile { get; set; }
}
public class Mobile
{
public ObjectId MobID { get; set; }
public string MobNumber { get; set; }
public bool IsPreferred { get; set; }
}
In addition to answer above, I can suggest following code if you want to deal directly with Bson for some reason:
BsonDocument _employee = new BsonDocument()
{
{ "Emp_ID", EmpInfo.EmpID },
{ "Emp_Name", EmpInfo.EmpName },
{ "Emp_Mobile", BsonArray.Create(EmpInfo.EmpMobile.Select(m => new BsonDocument()
{
{ "MobID" , new ObjectId() },
{ "MobNumber", m.MobNumber },
{ "IsPreferred", m.IsPreferred }
})) }
};
The reason of the error you've got is that BsonArray.Create creates an array of values, not an array of objects. See this question for details.