Summary of how to map MongoDB->RunCommand results to business classes in C#

foreword

I have never actually used MongoDB. Recently, I used it in a project and stepped on some pits. This article will introduce the problems encountered when mapping MongoDB->RunCommand results to business classes, and explore various methods.

The data in the Collection is like this:

Use the find command to query data:

db.runCommand({"find":"test", limit:2, sort:{AddTime:-1}})

The data structure returned by the query is like this, and the required data is in firstBatch:

{
 "cursor" : {
 "firstBatch" : [
  {
  "_id" : ObjectId("5ad0042944fd3929db6b869f"),
  "Name" : "李四",
  "AddTime" : ISODate("2018-04-12T01:13:18.965Z")
  },
  {
  "_id" : ObjectId("5ad003e844fd3929db6b869e"),
  "Name" : "张三",
  "AddTime" : ISODate("2018-04-02T01:11:18.965Z")
  }
 ],
 "id" : NumberLong(0),
 "ns" : "test.test"
 },
 "ok" : 1.0
}

The following will run the find command in C# to execute the query and map the results to a custom PersonInfo class.

private class PersonInfo
 {
  public string Id { get; set; }
  public string Name { get; set; }
  public DateTime AddTime { get; set; }
 }

It should be noted that the attribute Id in PersonInfo is somewhat different from the name _id in Document, and needs to be mapped. Because different deserialization methods are used in different ways, there will be related introductions below.

Using Json.NET

Because Json.NET is used more, the basic idea is to use it to deserialize Bson, but Json and Bson are different, and JsonConvert cannot be used.

But Json.NET provides a new package Newtonsoft.Json.Bsonto parse Bson data, and provides an example.

https://www.newtonsoft.com/json/help/html/DeserializeFromBson.htm

This example is a bit outdated, I have replaced the BsonReader used in it with BsonDataReader.

byte[] data = Convert.FromBase64String("MQAAAAJOYW1lAA8AAABNb3ZpZSBQcmVtaWVyZQAJU3RhcnREYXRlAMDgKWE8AQAAAA==");
MemoryStream ms = new MemoryStream(data);
using (BsonDataReader reader = new BsonDataReader(ms))
{
 JsonSerializer serializer = new JsonSerializer();
 
 Event e = serializer.Deserialize<Event>(reader);
 
 Console.WriteLine(e.Name);
 // Movie Premiere
}

It seems that it is enough to have a byte array, and then look at BsonDocument just has a method ToBson, and the obtained is byte[].

Idea: first map the result of RunCommand to BsonDocument, then find firstBatch, then ToBson it, and then use Newtonsoft.Json.Bsondeserialization.

Everything looks so easy!

Code now:

  var findCommand = BsonDocument.Parse("{\"find\":\"test\", limit:2, sort:{AddTime:-1}}");
  var findResult = database.RunCommand<BsonDocument>(findCommand)
  .GetElement("cursor").Value.ToBsonDocument()
  .GetElement("firstBatch").ToBsonDocument()
  .ToBson ();
  var personList = DeserializeBson <PersonList> (findResult);

An array cannot be the root element of a Bson, so a PersonList class is defined here:

private class PersonList
 {
  public string Name { get; set; }
  [JsonProperty(PropertyName = "Value")]
  public PersonInfo[] List { get; set; }
 }

 GetElement(“firstBatch”).ToBsonDocument() It should be noted that the data format returned in the above code is as follows:

Here, JsonProperty is used to map Value to List.

Also, the property names in the Person class are different from those in the document, and they also need to be mapped:

private class PersonInfo
 {
  [JsonProperty(PropertyName = "_id")]
  [JsonConverter(typeof(ObjectIdConverter))]
  public string Id { get; set; }
  public string Name { get; set; }
  public DateTime AddTime { get; set; }
 }

One is also used here  JsonConverter(typeof(ObjectIdConverter)) , because ObjectId cannot be directly converted to string, so an ObjectIdConverter class is defined:

public class ObjectIdConverter : JsonConverter
 {
  public override bool CanConvert(Type objectType)
  {
   return objectType == typeof(ObjectId);
  }
 
  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
   if (reader.Value != null)
   {
    var value = (byte[])reader.Value;
 
    var result = BitConverter.ToString(value).Replace("-", string.Empty).ToLowerInvariant();
 
    return result;
   }
 
   return string.Empty;
  }
 
  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
   throw new NotImplementedException();
  }
 }

There is also an important method, deserialization:

  public static T DeserializeBson<T>(byte[] data)
  {
   MemoryStream ms = new MemoryStream(data);
   using (BsonDataReader reader = new BsonDataReader(ms, false, DateTimeKind.Local))
   {
    JsonSerializer serializer = new JsonSerializer();
 
    T e = serializer.Deserialize<T>(reader);
    return e;
   }
  }

Now run the program to see:

But it feels so complicated, and it has gone through turns and turns.

Built-in with MongoDB .NET Driver

Speaking of such commonly used functions, the SDK should be built-in, so why bother to seek distance.

Since RunCommand can pass a type, the SDK should support deserializing custom types.

Try defining a new class based on the returned result:

  [BsonIgnoreExtraElements]
  private class FindCommandResult
  {
   [BsonElement("cursor")]
   public ResultCursor Cursor { get; set; }
  }
 
  [BsonIgnoreExtraElements]
  private class ResultCursor
  {
   [BsonElement("firstBatch")]
   public PersonInfo[] Batch { get; set; }
  }
 
  private class PersonInfo
  {
   [BsonId]
   [BsonRepresentation(BsonType.ObjectId)]
   public string Id { get; set; }
   public string Name { get; set; }
   public DateTime AddTime { get; set; }
  }

BsonIgnoreExtraElements, BsonElement, BsonId, and BsonRepresentation are all built-in attributes of the SDK, and you should be able to see the relevant functions at a glance.

Take a look at the code that queries this block:

   var findCommand = BsonDocument.Parse("{\"find\":\"test\", limit:1, sort:{AddTime:-1}}");
   var findResult = database.RunCommand<FindCommandResult>(findCommand);

The code runs:

This method is more direct and simpler than Json.NET.

Use lookup assignment

In order to deserialize and adapt to MongoDB, some types with no business meaning have been defined, and a lot of attribute annotations have been added, which feels that it is not direct and explicit enough.

Maybe only a few fields in the data are needed, or maybe there is no need to define types at all, just put them in a list.

I checked the definition of BsonDocument and found that it can be deserialized into BsonDocument when running the command, and then use GetElement to get the value of the relevant field according to the returned data structure.

code show as below:

   var findCommand = BsonDocument.Parse("{\"find\":\"test\", limit:2, sort:{AddTime:-1}}");
   var fdasheng178.comindResult = database.RunCommand<BsonDocument>(findCommand)
    .GetElement("cursor").Value.ToBsonDocument()
    .GetElelongboshyl.cnment("firstBatch").Value.AsBsonArray.Select(d =>
    {
     var dbd = d.AsBsonDocument;
     return new PersonInfo()
     {
      Id = dbd.GetElement("_id").Value.AsObjectId.ToString(),
      AddTime = dbd.GetElement("AddTime").Value.ToLocalTime(),
      Name = dbd.GetElement("Name").Value.ToString(),
     };
    }).hbs90.cnTjyz521.comoList();

Return directly after running List<PersonInfo> , which is closer to business needs.

This is the easiest way in this article.

If it is more general, you can automatically instantiate the relevant types through reflection here, but this is not as good as directly using the built-in deserialization method of the SDK.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324947480&siteId=291194637