CosmosDb. How to retrieve the _ts property from a Upsert operation - c#

Using the .NET 3 SDK for CosmosDB my upsert operation on an existing document does not return any of the extra properties stored in the document E.G. the _ts property. I would like to use this property in my app. Do I have to include a _ts property in my model or is there a way to retrieve _ts from the document?

Do I have to include a _ts property in my model?
If you're working with a typed model, then the answer is yes. You will need to create a property corresponding to _ts property of the response otherwise the deserializer will not be able to deserialize this.
Another option would be to work with dynamic type and do the conversion into the model yourself. For example, you could do something like:
var model = new Model()
{
Id = "some id",//should serialize to "id",
//other properties of the model
}
var item = (await container.UpsertItemAsync<dynamic>(model)).Resource;
Console.WriteLine(item.ToString());
Above would print something like the following:
{
"id": "783cf82c-f24c-4031-971d-bd5f604724d5",
"_rid": "xxxx",
"_self": "dbs/xxxx/colls/xxxx/docs/xxxx/",
"_etag": "\"xxxx\"",
"_attachments": "attachments/",
"_ts": 1621597233,
//other model properties
}
You can now extract the "_ts" property from the JSON.

Related

Make a dynamic Builder Filter for different types of nested documents - MongoDB C# Driver

I have a collection named some_collection. The schema for some_collection is like this: (The schema is dictated by the C# DTO)
{
_id: ObjectId(.....),
firstName: "fName",
lastName: "lName",
someType: 4,
innerObject: {
// see below
}
}
In my C# code, innerObject is an abstract class, and has multiple children classes. These children classes have variant properties, thus the documents in the MongoDB collection aren't all the same. The type of child class they are is demarcated by the someType field in an individual some_collection document. So, examples of 2 documents in audit_collection that have 2 different types of nested documents:
{
_id: ObjectId('first'),
firstName: "Jane",
lastName: "Smith",
someType: 0,
innerObject: {
prop1: "foo",
prop2: "bar",
aCollectionOfStrings: ["a", "b", "c"] // this is what I wanna search
}
},
{
_id: ObjectId('second'),
firstName: "John",
lastName: "Doe",
someType: 3,
innerObject: {
prop1: "baz",
prop2: "foobarbaz",
aCollectionOfObjects: [
{
myProp: "hello", // this is what I want to search
irrelevantProp: "blah"
},
{
myProp: "hello5", // this is what I want to search
irrelevantProp: "blah"
},
{
myProp: "hello1", // this is what I want to search
irrelevantProp: "blah"
}
]
}
}
The use case for this question is that I want to search for a string provided by a user, and this can exist in the firstname and lastname properties (which is at the top level of the document, and all the documents share it, so easy enough), and also some of the inner properties of the objects (since the nested inner documents are different in schema, it is more difficult to do). So for example:
For someType == 0, I'd search myDocument.innerObject.aCollectionOfStrings, whereas with someType == 3, I'd search each myDocument.innerObject.aCollectionOfObjects's myProp property.
In my C# code, if I pull the full collection, and then use LINQ operations on it, I have a C# function that determines how to search the full document (basically it checks the value of someType, and then based on that, it knows which properties to search), along with its nested document, and can do the filtration in the C# code.
However, after refactoring to using Builders Filters, I can't pass that C# filter function into the Filter (obviously, since all the Builder is doing is building a MongoDB query, I think):
filter = filter & Builders<MyOwnType>.Filter.Eq(a => CheckIfObjectHasString(a, search), true);
Where CheckIfObjectHasString is something like:
private bool CheckIfObjectHasString(MyOwnType doc, string search)
{
if(doc.someType == 0)
{
return doc.innerObject.aCollectionOfStrings.Where(s => s.ToLower().Contains(search)).Any();
} else if(doc.someType == 3) {
return doc.innerObject.aCollectionOfObjects.Where(d => d.myProp.ToLower().Contains(search)).Any();
} else if(...)
{
// etc.
}
}
One solution to this I thought of is maybe during document insertion, create a property on the some_collection document at the top-most level, that has all the searchable material, but that seems to be unclean. How can I build a filter like the above, without resorting to doing the processing in LINQ or the solution I just mentioned?
What I ended up doing was making raw BsonDocuments comparable to what I was looking for, and $oring them, and embedding the $regex in the BsonDocuments for their respective fields. I was unaware that when searching for documents in MongoDB, without the use of application code, your query is a document to compare against.
private BsonDocument FindSearch(string searchString)
{
// escape the searchString
var filterDocument = new BsonDocument();
var searchDocument = new BsonDocument();
searchDocument.Add(("$regex", $".*{searchString}.*"));
searchDocument.Add("$options", "i"); // to ignore case when performing the regex
var searchCriterias = new BsonArray(); // an array because I'll be $or-ing it
searchCriterias.Add(new BsonDocument("innerObject.aCollectionOfObjects.myProp", textSearch)); // access each nested object's property
searchCriterias.Add(new BsonDocument("innerObject.aCollectionOfStrings", textSearch)); // even though it's an array of strings, I can just search without having to iterate them
filterDocument.Add("$or", searchCriterias); // $or-ing it also saved me from having to check the existence of properties, before doing the search
}
And then, even though it's a BsonDocument, I was able to use it with my already existing Builders Filters e.g.:
existingFilter = existingFilter | FindSearch(theSearch); // existingFilter is FilterDefinition<MyOwnType>

MongoDB updateOne

I am trying to update an existing Mongo record, but am getting an "Additional information: Element name 'ID' is not valid'." error
I have a a BsonDocument "document" containing data that I retrieve from another source that looks like this:
{ "ID" : "ABCecdcf9851efbf0ef66953", ListingKey : "234534345345", "Created" : ISODate("2017-08-04T00:31:23.357Z"), "Modified" : ISODate("2017-08-04T00:31:23.358Z"), "Field1" : 1, "Field2" : "0.09", "Field3" : "1.10", "Field4" : "1", "Field5" : "1" }
Here is the C# code that I have written:
var collection = db.GetCollection<BsonDocument>("MyCollection");
//Hard coded for testing
var filter = Builders<BsonDocument>.Filter.Eq("ListingKey", "234534345345");
collection.UpdateOne(filter, document);
Is this related to the BsonDocument that I am trying to use to update? I found this documentation, which causes me to think that this is the cause. If so, is there a way to do an update with the format I have been provided?
https://docs.mongodb.com/getting-started/csharp/update/
I had a process working where it would delete the document and then add a new document, but for efficiency's sake I need this to update. Ideally it will only update the fields that are present in the BsonDocument and keep the existing fields in the Mongo document as is.
My problem was because I did not have the correct value when trying to update. My code works with this:
var collection = db.GetCollection<BsonDocument>("MyCollection");
//Hard coded for testing
var filter = Builders<BsonDocument>.Filter.Eq("ListingKey", "234534345345");
var update = Builders<BsonDocument>.Update.Set("Created", DateTime.UtcNow);
foreach (BsonElement item in document)
{
update = update.Set(item.Name, item.Value);
}
var result = collection.UpdateOne(filter, update);
I had to convert my string into an update BsonDocument.

Deserialize Nested JSON with C# Without Class Declaration

I am trying to deserialize an object dynamically but unsure what the syntax is:
The JSON looks like this:
"Id": 2750,
"Rev": 1,
"Fields": {
"System.AreaPath": "test",
"System.TeamProject": "proj",
"System.IterationPath": "Ipath",
"System.WorkItemType": "type"
}
I know they can be accessed like this:
var resultString = response.Content.ReadAsStringAsync().Result;
var jsonObject = JObject.Parse(resultString);
string ID= jsonObject["id"].ToString();
But I am not sure how to get to the values nested in Fields directly.
I know I can iterate through jsonObject["Fields"] but I want to access them via something like jsonObject["Fields\\System.AreaPath"].ToString(); or whichever is the correct syntax, if it exists that is.

ASP.NET getting indexed values when deserializing JSON into a dynamic object

So I have a JSON string that I am passing from an AJAX call to my controller. I have a list of indexed values that I am passing into a dynamic object.
I deserialize the JSON with
JsonConvert.DeserializeObject<dynamic>(s)
This is the output from that dynamic object:
"RolePermissions[0].RolePermissionId": "269",
"RolePermissions[0].HasAccess": "false",
"RolePermissions[1].RolePermissionId": "270",
"RolePermissions[1].HasAccess": "false",
"RolePermissions[2].RolePermissionId": "271",
"RolePermissions[2].HasAccess": "true",
"RolePermissions[3].RolePermissionId": "272",
"RolePermissions[3].HasAccess": "false"
When I try to access the a property of the object with
ssObj.RolePermissions[0].RolePermissionId
I get a RuntimeBinderException. I have tried to use JObject.Parse, which works great, but for some reason, the values in the array become out of order.
Any help would be greatly appreciated. Thanks!
When you try to do RolePermissions[0].RolePermissionId you are trying to access a nested collection containing an object with a property RolePermissionId at index 0. But your JSON doesn't represent a hierarchy of objects, it represents a single flat object with key/value pairs whose keys contain periods and brackets. Since c# identifiers don't allow such characters so you have no way to access such property values using dynamic directly.
Instead, your options include:
Take advantage of the fact that JsonConvert.DeserializeObject<dynamic>(s) actually returns a JObject and use its dictionary indexer:
var ssObj = JsonConvert.DeserializeObject<dynamic>(s);
var rolePermissionId = (string)ssObj["RolePermissions[0].RolePermissionId"];
If you prefer a slightly more typed solution, you could deserialize to a Dictionary<string, dynamic>:
var ssDict = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(s);
var rolePermissionId = (string)ssDict["RolePermissions[0].RolePermissionId"];
Or for a much more statically typed solution, parse explicitly to a JObject and use LINQ to JSON:
var jObj = JObject.Parse(s);
var rolePermissionId = (string)jObj["RolePermissions[0].RolePermissionId"];
Sample fiddle showing the various options.
If you are in control of the data being sent via AJAX then make sure the data sent is properly formatted.
In order to be able to deserialize variable s like:
var ssObj = JsonConvert.DeserializeObject<dynamic>(s);
and access the resulting object in this manner:
ssObj.RolePermissions[0].RolePermissionId
then the JSON value in s, based on your sample and desired behavior, would have to look like this:
{
"RolePermissions": [
{
"RolePermissionId": "269",
"HasAccess": "false"
},
{
"RolePermissionId": "270",
"HasAccess": "false"
},
{
"RolePermissionId": "271",
"HasAccess": "true"
},
{
"RolePermissionId": "272",
"HasAccess": "false"
}
]
}
This quick unit test showed that it is possible to get indexed values when deserializing JSON into a dynamic object
[TestClass]
public class UnitTest1 {
[TestMethod]
public void GetIndexedValuesWhenDeserializingJSONIntoDynamicObject() {
var s = #"
{
'RolePermissions': [
{
'RolePermissionId': '269',
'HasAccess': 'false'
},
{
'RolePermissionId': '270',
'HasAccess': 'false'
},
{
'RolePermissionId': '271',
'HasAccess': 'true'
},
{
'RolePermissionId': '272',
'HasAccess': 'false'
}
]
}
";
var ssObj = JsonConvert.DeserializeObject<dynamic>(s);
var result = ssObj.RolePermissions[0].RolePermissionId;
Assert.AreEqual("269", (string)result);
}
}
So you need to make sure you are sending well formatted JSON to your controller to achieve desired behavior.

MongoDB: update only specific fields

I am trying to update a row in a (typed) MongoDB collection with the C# driver. When handling data of that particular collection of type MongoCollection<User>, I tend to avoid retrieving sensitive data from the collection (salt, password hash, etc.)
Now I am trying to update a User instance. However, I never actually retrieved sensitive data in the first place, so I guess this data would be default(byte[]) in the retrieved model instance (as far as I can tell) before I apply modifications and submit the new data to the collection.
Maybe I am overseeing something trivial in the MongoDB C# driver how I can use MongoCollection<T>.Save(T item) without updating specific properties such as User.PasswordHash or User.PasswordSalt? Should I retrieve the full record first, update "safe" properties there, and write it back? Or is there a fancy option to exclude certain fields from the update?
Thanks in advance
Save(someValue) is for the case where you want the resulting record to be or become the full object (someValue) you passed in.
You can use
var query = Query.EQ("_id","123");
var sortBy = SortBy.Null;
var update = Update.Inc("LoginCount",1).Set("LastLogin",DateTime.UtcNow); // some update, you can chain a series of update commands here
MongoCollection<User>.FindAndModify(query,sortby,update);
method.
Using FindAndModify you can specify exactly which fields in an existing record to change and leave the rest alone.
You can see an example here.
The only thing you need from the existing record would be its _id, the 2 secret fields need not be loaded or ever mapped back into your POCO object.
It´s possible to add more criterias in the Where-statement. Like this:
var db = ReferenceTreeDb.Database;
var packageCol = db.GetCollection<Package>("dotnetpackage");
var filter = Builders<Package>.Filter.Where(_ => _.packageName == packageItem.PackageName.ToLower() && _.isLatestVersion);
var update = Builders<Package>.Update.Set(_ => _.isLatestVersion, false);
var options = new FindOneAndUpdateOptions<Package>();
packageCol.FindOneAndUpdate(filter, update, options);
Had the same problem and since I wanted to have 1 generic method for all types and didn't want to create my own implementation using Reflection, I end up with the following generic solution (simplified to show all in one method):
Task<bool> Update(string Id, T item)
{
var serializerSettings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
};
var bson = new BsonDocument() { { "$set", BsonDocument.Parse(JsonConvert.SerializeObject(item, serializerSettings)) } };
await database.GetCollection<T>(collectionName).UpdateOneAsync(Builders<T>.Filter.Eq("Id", Id), bson);
}
Notes:
Make sure all fields that must not update are set to default value.
If you need to set field to default value, you need to either use DefaultValueHandling.Include, or write custom method for that update
When performance matters, write custom update methods using Builders<T>.Update
P.S.: It's obviously should have been implemented by MongoDB .Net Driver, however I couldn't find it anywhere in the docs, maybe I just looked the wrong way.
Well there are many ways to updated value in mongodb.
Below is one of the simplest way I choose to update a field value in mongodb collection.
public string UpdateData()
{
string data = string.Empty;
string param= "{$set: { name:'Developerrr New' } }";
string filter= "{ 'name' : 'Developerrr '}";
try
{
//******get connections values from web.config file*****
var connectionString = ConfigurationManager.AppSettings["connectionString"];
var databseName = ConfigurationManager.AppSettings["database"];
var tableName = ConfigurationManager.AppSettings["table"];
//******Connect to mongodb**********
var client = new MongoClient(connectionString);
var dataBases = client.GetDatabase(databseName);
var dataCollection = dataBases.GetCollection<BsonDocument>(tableName);
//****** convert filter and updating value to BsonDocument*******
BsonDocument filterDoc = BsonDocument.Parse(filter);
BsonDocument document = BsonDocument.Parse(param);
//********Update value using UpdateOne method*****
dataCollection.UpdateOne(filterDoc, document);
data = "Success";
}
catch (Exception err)
{
data = "Failed - " + err;
}
return data;
}
Hoping this will help you :)

Categories