Page 12 - MSDN Magazine, March 2019
P. 12
can affect with EF Core is container names and mapping entities to be stored in different containers.
You can override the default container name with the Has- DefaultContainerName method in OnConfiguring. For example, the following will use ExpanseDocuments as the default name instead of ExpanseDbContext:
modelBuilder.HasDefaultContainerName("ExpanseDocuments");
If you’ve determined that you want to split data into different containers, you can map a new container name for particular entities. Here’s an example that specifies the Ship entity from the previous article into a container called ExpanseShips:
modelBuilder.Entity<Ship>().ToContainer("ExpanseShips");
You can target as many entities to a single container as you want. The default container already demonstrates this. But you could use ToContainer(“ExpanseShips”) with other entities, as well, if you wanted.
Figure 2 The Expanse Classes
What happens when you add a new container to an existing database in this way? As I noted in Part 1, the only way to have EF Core create a database or container is by calling context.Data- base.EnsureCreated. EF Core will recognize what does and doesn’t already exist and create any new containers as needed.
If you change the default container name, EF will create the new container and will work with that container going forward. But any data in the original container will remain there.
Because Azure Cosmos DB doesn’t have the ability to rename an existing container, the official recommendation is to move the data into the new collection, perhaps with a bulk executor library, such as the one at bit.ly/2RbpTvp. The same holds true if you change the mapping for an entity to a different container. The original data won’t be moved and you’ll be responsible for ensuring that the old items are transferred. Again, it’s probably more reasonable to do that one-time move outside of EF Core.
I also tested out adding graphs of Consortium with Ships where the documents would end up in separate containers in the database. When reading that data, I was able to write a query for Consortia that eager-loaded its ship data, for example:
context.Consortia.Include(c=>c.Ships).FirstOrDefault()
EF Core was able to retrieve the data from the separate containers and reconstruct the object graph.
Owned Entities Get Embedded
Within Parent Documents
In Part 1, you saw that related entities were stored in their own doc- uments. I’ve listed the Expanse classes in Figure 2 as a reminder of the example model. When I built a graph of a Consortium with Ships, each object was stored as a separate document with foreign keys that allow EF Core or other code to connect them back up again. That’s a very relational concept, but because consortia and ships are unique entities that have their own identity keys, this is how EF Core will persist them. But EF Core does have an under- standing of document database and embedded documents, which you can witness when working with owned entities. Notice that the Origin type doesn’t have a key property and it’s used as a property of both Ship and of Consortium. It will be an owned entity in my model.YoucanreadmoreabouttheEFCoreOwnedEntityfeature in my April 2018 Data Points article at msdn.com/magazine/mt846463.
In order for EF Core to comprehend an owned type so that it can map it to a database, you need to configure it either as a data annotation or (always my preference) a fluent API configuration. The latter happens in the DbContext OnConfiguring method as I’m doing here:
modelBuilder.Entity<Ship>().OwnsOne(s=>s.Origin); modelBuilder.Entity<Consortium>().OwnsOne(s=>s.Origin);
Here’s some code for adding a new Ship, along with its origin, to a consortium object:
consortium.Ships.Add(new Ship{ShipId=Guid.NewGuid(),ShipName="Nathan Hale 3rd", Origin= new Origin {Date=DateTime.Now,
Location="Earth"}});
When the consortium is saved via the ExpanseContext, the new ship is also saved into its own document.
Figure 3 displays the document for that Ship with its Origin represented as an embedded document. A document database Data Points
public class Consortium {
public Consortium() {
Ships=new List<Ship>();
Stations=new List<Station>(); }
public Guid ConsortiumId { get; set; } public string Name { get; set; } public List<Ship> Ships{get;set;} public List<Station> Stations{get;set;} public Origin Origin{get;set;}
}
public class Planet {
public Guid PlanetId { get; set; }
public string PlanetName { get; set; } }
public class Ship {
public Guid ShipId {get;set;} public string ShipName {get;set;} public int PlanetId {get;set;} public Origin Origin{get;set;}
}
public class Origin {
public DateTime Date{get;set;}
public String Location{get;set;} }
Figure 3 A Ship Document with
an Origin Sub-Document Embedded
{
"ShipId": "e5d48ffd-e52e-4d55-97c0-cee486a91629", "ConsortiumId": "60ccb22d-4422-45b2-a54a-71fa240435b3", "Discriminator": "Ship",
"PlanetId": 0,
"ShipName": "Nathan Hale 3rd",
"id": "c2bdd90f-cb6a-4a3f-bacf-b0b3ac191662", "Origin": {
"ShipId": "e5d48ffd-e52e-4d55-97c0-cee486a91629", "Date": "2019-01-22T11:40:29.117453-05:00", "Discriminator": "Origin",
"Location": "Earth"
},
"_rid": "cgEVAKklUPgCAAAAAAAAAA==",
"_self": "dbs/cgEVAA==/colls/cgEVAKklUPg=/docs/
cgEVAKklUPgCAAAAAAAAAA==/",
"_etag": "\"0000a43b-0000-0000-0000-5c47477d0000\"", "_attachments": "attachments/",
"_ts": 1548175229
}
8 msdn magazine