How to cast Azure DocumentDB Document class to my

2020-08-23 08:29发布

问题:

Is there a way to cast the Microsoft.Azure.Documents.Document object to my class type?

I've written an Azure Function class, with a CosmosDBTrigger. The trigger receives an array of Microsoft.Azure.Documents.Document. I like having that Document class so that I can access the meta data about the record itself, but I also would like to interact with my data from my class type in a static way.

I see the JSON representation of my data when I call ToString. Should I manually convert that JSON to my class type using Newtonsoft?

回答1:

If you need to map your Document to your POCO in the function then the easiest way to do that is what you suggested.

Call the document.Resource.ToString() method and use DeserializeObject from JSON.NET or the json library you prefer. JSON.NET is recommended however as Microsoft's CosmosDB libraries use it as well.

Your mapping call will look like this:

var yourPoco = JsonConvert.DeserializeObject<YourPocoType>(document.Resource.ToString())



回答2:

While solution offered by Nick Chapsas works, I would like to offer a few better options.

Preferred solution - improve your model

First, if you are interested in the extra meta fields then you can always include the chosen properties into your data access model and they will be filled in. for example:

public class Model
{
    public String id { get; set; }
    public String _etag { get; set; }
    //etc.
}

Then you can use the existing API for deserializing thats explicit and familiar to all. For example:

var explicitResult = await client.ReadDocumentAsync<Model>(documentUri);
Model explicitModel = explicitResult.Document;

If you want the next layer model (ex: domain model) to NOT have those storage-specific meta fields then you need to transform to another model, but that is no longer a cosmosDB-level issue and there are plenty of generic mappers to convert between POCOs.

This is the IMHO cleanest and recommended way to handing data access in cosmosDB if you work on strongly typed document models.

Alternative: dynamic

Another trick is to use dynamic as the intermediate casting step. This is short and elegant in a way, but personally using dynamic always feels a bit dirty:

var documentResult = await client.ReadDocumentAsync(documentUri);
Model dynamicModel = (dynamic)documentResult.Resource;

Alternative: read JObject

Another alternative is to read the document as NewtonSoft's JObject. This would also include all the meta fields and you could cast it further yourself without all the extra hopping between string representations. Example:

var jObjectResult = await client.ReadDocumentAsync<JObject>(documentUri);
Model JObjectResult = jObjectResult.Document.ToObject<Model>();

Alternative: Document + JObject at the same time

Should you really-really want to avoid the document level meta fields in model AND still access them then you could use a little reflection trick to get the JObject from the Document instance:

var documentResult = await client.ReadDocumentAsync(documentUri);
Document documentModel = documentResult.Resource;

var propertyBagMember = documentResult.Resource.GetType()
    .GetField("propertyBag", BindingFlags.NonPublic| BindingFlags.Instance);
Model reflectionModel = ((JObject)propertyBagMember.GetValue(documentResult.Resource))
    .ToObject<Model>();

Beware that the reflection trick is relying on the internal implementation details and it is not subject to backwards compatibility guarantees by library authors.