Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
EF Core was designed to make it easy to work with data that follows a schema defined in the model. However one of the strengths of Azure Cosmos DB is the flexibility in the shape of the data stored.
Accessing the raw JSON
It is possible to access the properties that are not tracked by EF Core through a special property in shadow-state named "__jObject"
that contains a JObject
representing the data received from the store and data that will be stored:
using (var context = new OrderContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
var order = new Order
{
Id = 1, ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" }, PartitionKey = "1"
};
context.Add(order);
await context.SaveChangesAsync();
}
using (var context = new OrderContext())
{
var order = await context.Orders.FirstAsync();
var orderEntry = context.Entry(order);
var jsonProperty = orderEntry.Property<JObject>("__jObject");
jsonProperty.CurrentValue["BillingAddress"] = "Clarence House";
orderEntry.State = EntityState.Modified;
await context.SaveChangesAsync();
}
using (var context = new OrderContext())
{
var order = await context.Orders.FirstAsync();
var orderEntry = context.Entry(order);
var jsonProperty = orderEntry.Property<JObject>("__jObject");
Console.WriteLine($"First order will be billed to: {jsonProperty.CurrentValue["BillingAddress"]}");
}
{
"Id": 1,
"PartitionKey": "1",
"TrackingNumber": null,
"id": "1",
"Address": {
"ShipsToCity": "London",
"ShipsToStreet": "221 B Baker St"
},
"_rid": "eLMaAK8TzkIBAAAAAAAAAA==",
"_self": "dbs/eLMaAA==/colls/eLMaAK8TzkI=/docs/eLMaAK8TzkIBAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-683e-0a12bf8d01d5\"",
"_attachments": "attachments/",
"BillingAddress": "Clarence House",
"_ts": 1568164374
}
Warning
The "__jObject"
property is part of the EF Core infrastructure and should only be used as a last resort as it is likely to have different behavior in future releases.
Note
Changes to the entity will override the values stored in "__jObject"
during SaveChanges
.
Using CosmosClient
To decouple completely from EF Core get the CosmosClient object that is part of the Azure Cosmos DB SDK from DbContext
:
using (var context = new OrderContext())
{
var cosmosClient = context.Database.GetCosmosClient();
var database = cosmosClient.GetDatabase("OrdersDB");
var container = database.GetContainer("Orders");
var resultSet = container.GetItemQueryIterator<JObject>(new QueryDefinition("select * from o"));
var order = (await resultSet.ReadNextAsync()).First();
Console.WriteLine($"First order JSON: {order}");
order.Remove("TrackingNumber");
await container.ReplaceItemAsync(order, order["id"].ToString());
}
Missing property values
In the previous example we removed the "TrackingNumber"
property from the order. Because of how indexing works in Azure Cosmos DB, queries that reference the missing property somewhere else than in the projection could return unexpected results. For example:
using (var context = new OrderContext())
{
var orders = await context.Orders.ToListAsync();
var sortedOrders = await context.Orders.OrderBy(o => o.TrackingNumber).ToListAsync();
Console.WriteLine($"Number of orders: {orders.Count}");
Console.WriteLine($"Number of sorted orders: {sortedOrders.Count}");
}
The sorted query actually returns no results. This means that one should take care to always populate properties mapped by EF Core when working with the store directly.
Note
This behavior might change in future versions of Azure Cosmos DB. For instance, currently if the indexing policy defines the composite index {Id/? ASC, TrackingNumber/? ASC)}, then a query that has 'ORDER BY c.Id ASC, c.Discriminator ASC' would return items that are missing the "TrackingNumber"
property.