Access MongoDB Rest service with C# - mongodb-.net-driver

I'm a beginner with mongoDb, I would like to access MongoDb rest service, the data I retrieved is json type. My question is, how do you parse this data ? I don't find any MongoDb api which allows me to query it easily. So what would you do ?
Here's an example of the data, I queried the key "Name" which returned me on row thanks to this url:
http://localhost:28017/MyDatabase/MyCollection/?filter_Key=Name
{
"offset" : 0,
"rows": [
{ "_id" : { "$binary" : "fXvnbtlMhU24EWg9NiY5QQ==", "$type" : "03" }, "Key" : "Name", "Value" : "John Smith" }
],
"total_rows" : 1 ,
"query" : { "Key" : "Name" } ,
"millis" : 0
}
And I would like to retrieve the Value "John Smith"
Thank's
[EDIT]
I've managed to get {"Value": "John Smith"} out of my json. oh!! See this ugly code:
var urlToFetch = "http://`localhost`:28017/MyDatabase/MyCollection/?filter_Key=Name";
var jsonData = GetDataFrom(urlToFetch);
var value = JsonConvert.DeserializeObject(jsonData);
foreach (var key in ((JObject)value)["rows"].Values())
{
key.Parent.Last;
}
It's not perfect, I still don't get my John Smith But there're must be a better way without manually parsing, aren't there ?

Here the solution guys:
public class yourclass
{
public void transformJsonToObject()
{
JsonSerializer serializer = new JsonSerializer();
var value = JsonConvert.DeserializeObject<ResultViewModel>(jsonData);
}
}
public class ResultViewModel
{
[JsonProperty("offset")]
public int Offset;
[JsonProperty("rows")]
public TestViewModel[] Rows;
}
public class TestViewModel
{
[JsonProperty("_id")]
public TestArray Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class TestArray
{
[JsonProperty("$binary")]
public string Binary { get; set; }
[JsonProperty("$type")]
public string Type { get; set; }
}

Related

MongoDB c# Deserialization - property not recognized

I'm upgrading from using BsonDocument everywhere, to using deserialization on POCO objects.
Overall the objects are populated with correct values, but i'm having a problem with the code below, values on the EventReferenceEntry.Id property - which are always 0.
Perhaps worth knowing, is that if i remove "BsonIgnoreExtraElements" attribute from the EventReferenceEntry class, i get an error "Element 'i' does not match any field or property of class "
I've also tried setting EventReferenceEntry.Id to Int64 and UInt64, but no difference.
The driver is version 2.7.3, i tried it with a fresh installation of the latest version, but it's the same problem.
The database is a series of events. An event has:
_id = Int64
_t = Int32 (the type of the event)
_r = an array of objects (references to other objects, entities or events, that are relevant.)
C# Code of the POCO objects
[BsonIgnoreExtraElements]
public class EventEntry
{
[BsonElement("_id")]
public ulong Id { get; set; }
[BsonElement("_t")]
public int Type { get; set; }
public DateTime Time { get { return new DateTime((long)Id, DateTimeKind.Utc); } }
[BsonElement("_r")]
public List<EventReferenceEntry> References { get; set; }
}
[BsonIgnoreExtraElements]
public class EventReferenceEntry
{
[BsonElement("i")]
public UInt64 Id { get; set; }
[BsonElement("n")]
public string Name { get; set; }
[BsonElement("a")]
public int Asset { get; set; }
public EventReferenceEntry()
{
}
}
An example database entry
{
"_id" : NumberLong(637684658186492532),
"_t" : 1058,
"_r" : [
{
"n" : "p",
"i" : NumberLong(637662370697662760)
},
{
"n" : "a",
"a" : 1202
},
{
"n" : "o",
"i" : NumberLong(637684655676255124),
"a" : 2934
}
]
}
Found the problem and solution.
A property in the class (in my case EventReferenceEntry) cannot be named “Id” or “id”, I guess that a property named that is assumed to be bound to the _id bson property. It can however be named “ID” or “Ident” or anything else, and the value gets assigned.

C# Mongo db driver updating an existing document

I have an existing document in mongo db with certain properties. When i am trying to update this document with a document with the same id but having more properties it is not updating it. I am using mongo db driver version 2.4.4
Following is the existing document in my db
{
"_id" : "1234",
"AccountNumber" : "3453535345354",
"Name" : "new1",
"PhoneNumber" : "34534535353534543",
"ETag" : "6ba32e6e-3808-41f5-9b28-0ea882d9c629",
"Id" : "1234"
}
Now when i am trying to update/ replace this document with the following document it does not work:
{
"AccountNumber": "3453535345354",
"Name":"new1",
"PhoneNumber":"34534535353534543",
"TempProperty":"something new",
"ETag" : "6ba32e6e-3808-41f5-9b28-0ea882d9c629",
"Id" : "1234",
"_id" : "1234"
}
Following is the C# code for my upsert method:
public async Task UpsertDocument(string collectionId, string documentId, BsonDocument item)
{
var collection = database.GetCollection<BsonDocument>(collectionId);
var initialCorrelationId = item["ETag"].AsString;
item["ETag"] = Guid.NewGuid().ToString();
var builders = Builders<BsonDocument>.Filter;
var filter = builders.Eq(x => x["Id"], documentId) & builders.Eq(x => x["ETag"], initialCorrelationId);
var options = new FindOneAndReplaceOptions<BsonDocument, BsonDocument>
{
ReturnDocument = ReturnDocument.Before,
IsUpsert = true
};
try
{
item["Id"] = documentId;
item["_id"] = documentId;
await collection.FindOneAndReplaceAsync(filter, item, options);
}
catch (MongoCommandException ex) when (ex.Code == 11000)
{
throw new ConcurrencyException(
$"Error upserting item with id {documentId} and etag {item["Etag"]}{Environment.NewLine}{item.ToJson()}");
}
}
Any help would be much appreciated.
Assuming you have a class as follows:
public class Thing
{
public ObjectId Id { get; set; }
public string AccountNumber { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string ETag { get; set; }
public string SecondId { get; set; }
}
The following code does what you want:
var context = new Context();
var tempProperty = "newValue";
var id = "1234"; // This should be unique
var builder = Builders<Thing>.Filter;
var filter = builder.Eq(x => x.SecondId, id);
var update = Builders<Thing>.Update
.Set("TempProperty", tempProperty);
context.ThingCollection.UpdateOne(filter, update, new UpdateOptions() {IsUpsert = true});
I just renamed Id to SecondId.

How can I convert class properties into uppercase and rename few without Attribute in elasticsearch with c#

I am using elasticsearch in C# to put some data into elasticsearch. I have kept my C# class & properties as per database (PascalCase). However, my requirement is to convert all properties into Uppercase and also change the name of few to another name. Same should be possible for class name as well.
I do not want to achieve this via Data annotations. Is there any way which can be made generic for all C# classes?
I am using NEST 5.X version.
For example,
class Foo
{
public string thisMessage {get; set; }
public string anotherMessage {get; set; }
}
should convert into
class FOOABC
{
public string THISMESSAGE {get; set; }
public string ANOTHER {get; set; }
}
It's possible to do both with NEST without using attributes:
to change the casing of POCO property names when serialized and sent to Elasticsearch, use DefaultFieldNameInferrer(Func<string, string>)
to change the name of the type and rename properties, use InferMappingFor<T>() with TypeName() and Rename()
Here's an example
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var defaultIndex = "default-index";
var connectionSettings = new ConnectionSettings(pool)
.DefaultFieldNameInferrer(s => s.ToUpperInvariant())
.InferMappingFor<Foo>(m => m
.TypeName("FOOABC")
.Rename(p => p.anotherMessage, "ANOTHER")
)
.DefaultIndex(defaultIndex);
var client = new ElasticClient(connectionSettings);
if (client.IndexExists(defaultIndex).Exists)
client.DeleteIndex(defaultIndex);
var indexResponse = client.Index(new Foo
{
thisMessage = "this message",
anotherMessage = "another message"
});
}
class Foo
{
public string thisMessage { get; set; }
public string anotherMessage { get; set; }
}
the index request and response
POST http://localhost:9200/default-index/FOOABC?pretty=true
{
"THISMESSAGE": "this message",
"ANOTHER": "another message"
}
Status: 201
{
"_index" : "default-index",
"_type" : "FOOABC",
"_id" : "AVtaHKDcii5CLKx9KIcZ",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : true
}

Elasticsearch NEST partial update of anonymous object with an empty nested object

Trying to "reset" a nested object in a document but it will not set it back to empty.
I have a POCO:
public class StreetAddress
{
public int HouseNumber { get; set; }
public string Street { get; set; }
}
public class FullAddress
{
public string City{ get; set; }
public string State { get; set; }
public StreetAddress StreetAddress { get; set; }
public int Zip { get; set; }
public List<string> Codes { get; set; }
}
So currently if I created this new document for FullAddress with the StreetAddress already set the document looks like this when queried:
"_source": {
"city": "Los Angeles",
"state": "California",
"zip": 90019,
"streetAddress": {
"houseNumber": 1,
"street": "Apple Street"
},
"codes" : [
{ "la-601" }
]
}
Now I want to call NEST client update to reset the StreetAddress nested object:
StreetAddress localStreetAddress = new StreetAddress();
var partialAddress = new
{
City = "NewCity",
Zip = 11111,
StreetAddress = localStreetAddress
};
this._client.Update<ElasticsearchProject, object>(update => update
.Id(1)
.Doc(partialAddress)
);
The final outcome I was hoping for when I query would be after the above update call:
"_source": {
"city": "NewCity",
"state": "California",
"zip": 11111,
"streetAddress": {}
}
However, the partial update does two things that is undesired:
It only updates the City and Zip fields and leaves the StreetAddress
as it was before and doesn't clear it out to empty or null.
It clears out the Codes list to empty since the partial update doesn't include the list.
I know I can set the StreetAddress to null and add the JSON property to include null like this:
[JsonProperty(NullValueHandling = NullValueHandling.Include)]
public StreetAddress StreetAddress { get; set; }
but all that would result is that the document update would set it to null and not empty and I am not sure if that is a desired result for the document:
"_source": {
"city": "NewCity",
"state": "California",
"zip": 11111,
"streetAddress": null
}
Not sure if there was a way to do a partial update without going down the script path to set the nested object back to empty.
Nested objects are actually mapped as separate hidden documents within Elasticsearch and as you have found, NEST by default is configured not to send through null values. the end result is that the nested type is not affected by your partial update to the top-level properties.
One way of handling this is to simply re-index the document again without the StreetAddress. An update is essentially a delete and insert behind the scenes anyway.
As an example
void Main()
{
var settings = new ConnectionSettings(new Uri("http://localhost:9200"), "address");
var client = new ElasticClient(settings);
// create the index
client.CreateIndex("address", c => c
.AddMapping<FullAddress>(m => m
.MapFromAttributes()
)
);
// index our document
client.Index(new FullAddress
{
City = "Los Angeles",
State = "California",
Zip = 90019,
Codes = new List<string>
{
"la-601"
},
StreetAddress = new StreetAddress
{
HouseNumber = 1,
Street = "Apple Street"
}
}, c => c.Id(1));
// Now at some later point, we need to update it,
// so get the current document
var response = client.Get<FullAddress>(1);
// make the changes to the current document
var address = response.Source;
address.StreetAddress = null;
// and re-index
client.Index<FullAddress>(address, c => c.Id(1));
}
public class StreetAddress
{
public int HouseNumber { get; set; }
public string Street { get; set; }
}
public class FullAddress
{
public string City { get; set; }
public string State { get; set; }
public StreetAddress StreetAddress { get; set; }
public int Zip { get; set; }
public List<string> Codes { get; set; }
}
The end result is as expected
{
"_index": "address",
"_type": "fulladdress",
"_id": "1",
"_version": 2,
"found": true,
"_source": {
"city": "Los Angeles",
"state": "California",
"zip": 90019,
"codes": [
"la-601"
]
}
}
You may also want to use optimistic concurrency control when doing this too, to handle any changes between the get and index.

Getting an Enum to display on client side

I'm having hard time understanding how to convert an Enum value to it's corresponding name. My model is as follows:
public class CatalogRule
{
public int ID { get; set; }
[Display(Name = "Catalog"), Required]
public int CatalogID { get; set; }
[Display(Name = "Item Rule"), Required]
public ItemType ItemRule { get; set; }
public string Items { get; set; }
[Display(Name = "Price Rule"), Required]
public PriceType PriceRule { get; set; }
[Display(Name = "Value"), Column(TypeName = "MONEY")]
public decimal PriceValue { get; set; }
[Display(Name = "Exclusive?")]
public bool Exclude { get; set; }
}
public enum ItemType
{
Catalog,
Category,
Group,
Item
}
public enum PriceType
{
Catalog,
Price_A,
Price_B,
Price_C
}
A sample result from .net API:
[
{
$id: "1",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 1,
CatalogID: 501981,
ItemRule: 0,
Items: "198",
PriceRule: 1,
PriceValue: 0.5,
Exclude: false
},
{
$id: "2",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 2,
CatalogID: 501981,
ItemRule: 2,
Items: "9899",
PriceRule: 2,
PriceValue: 10.45,
Exclude: false
}
]
So in this example, I need to get Catalog for results[0].ItemRule & Price A for results[0].PriceRule. How can I accomplish this in BreezeJS??
This is easy to do in ASP.NET Web API, because it is an out-of-box feature in the default JSON serializer (Json.NET).
To see strings instead of enum numbers in JSON, just add an instance of StringEnumConverter to JSON serializer settings during app init:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
UPDATE: Yep, you right, this is not help with Breeze.js. Ok, you can anyway do a little magic to make enums work like strings (while new version with fix is not released).
Create a custom ContextProvider which updates all integer enum values in metadata to strings. Here it is:
public class StringEnumEFContextProvider<T> : EFContextProvider<T>
where T : class, new()
{
protected override string BuildJsonMetadata()
{
XDocument xDoc;
if (Context is DbContext)
{
xDoc = GetCsdlFromDbContext(Context);
}
else
{
xDoc = GetCsdlFromObjectContext(Context);
}
var schemaNs = "http://schemas.microsoft.com/ado/2009/11/edm";
foreach (var enumType in xDoc.Descendants(XName.Get("EnumType", schemaNs)))
{
foreach (var member in enumType.Elements(XName.Get("Member", schemaNs)))
{
member.Attribute("Value").Value = member.Attribute("Name").Value;
}
}
return CsdlToJson(xDoc);
}
}
And use it instead of EFContextProvider in your Web API controllers:
private EFContextProvider<BreezeSampleContext> _contextProvider =
new StringEnumEFContextProvider<BreezeSampleContext>();
This works well for me with current Breeze.js version (1.1.3), although I haven't checked other scenarios, like validation...
UPDATE: To fix validation, change data type for enums in breeze.[min|debug].js, manually (DataType.fromEdmDataType function, dt = DataType.String; for enum) or replace default function during app init:
breeze.DataType.fromEdmDataType = function (typeName) {
var dt = null;
var parts = typeName.split(".");
if (parts.length > 1) {
var simpleName = parts[1];
if (simpleName === "image") {
// hack
dt = DataType.Byte;
} else if (parts.length == 2) {
dt = DataType.fromName(simpleName);
if (!dt) {
if (simpleName === "DateTimeOffset") {
dt = DataType.DateTime;
} else {
dt = DataType.Undefined;
}
}
} else {
// enum
dt = DataType.String; // THIS IS A FIX!
}
}
return dt;
};
Dirty, dirty hacks, I know... But that's the solution I found
There will be a new release out in the next few days where we "change" breeze's enum behavior ( i.e. break existing code with regards to enums). In the new release enums are serialized and queried by their .NET names instead of as integers. I will post back here when the new release is out.

Resources