Page 16 - MSDN Magazine, October 2017
P. 16

There are some enhancements to the SecretIdentity property since the earlier sample. First, the property originally was public, with a public getter and a private setter. This allowed EF Core to per- sist it in the same way as in earlier versions of EF. Now, however, SecretIdentity is declared as a private property yet I’ve defined no backing property. When it comes time to persist, EF Core is able to infer a backing property so it can store and retrieve this data without any additional mapping on my part. The Identify method, where you can specify a first and last name for the secret identity, was in the earlier sample. But in that case, if you wanted to read that value, you could access it through the public property. Now that it’s hidden, I’ve added a new method, RevealSecretIdentity, which will use the PersonFullName.IsEmpty method to determine if the property is populated or not. If so, then it returns the FullName of the SecretIdentity. But if the person’s true identity wasn’t identified, the method returns the string: “It’s a secret.”
There’s a new property in Samurai, a bool called IsDirty. Any time I modify the Samurai properties, I set IsDirty to true. I’ll use that value elsewhere to determine if I need to call SaveChanges on the Samurai.
So throughout this aggregate, there’s no way to get around the rules I built into the entities and the root, Samurai. The only way to create, modify or read Entrance, Quotes and SecretIdentity is through the constrained logic built into Samurai, which, as the aggregate root, is guarding the entire aggregate.
Mapping to the Data Store with EF Core 2.0
The focus of the previous article was on how EF Core 2.0 is able to persist and retrieve data mapped to these constrained classes. With this enhanced domain model, EF Core is still able to work out most of the mappings even with things so tightly encapsulated in the Samurai class. In a few cases I do have to provide a little help to the DbContext to make sure it comprehends how these classes map to the database, as shown in Figure 6.
Figure 6 The SamuraiContext DbContext Class
Not a lot has changed in the SamuraiContext since the first sam- ple from my first article, but there are a few things to point out as reminders. For example, the OwnsOne mapping lets EF Core know that SecretIdentity is an Owned Entity and that its properties should be persisted *as though they were individual properties of Samurai. For the sake of this sample, I’m hardcoding the provider in the On- Configuring method as opposed to leveraging dependency injection and inversion of control (IoC) services. As mentioned in the first ar- ticle, EF Core can figure out the one-to-one relationship between Sa- murai and Entrance, but I have to express the relationship in order to access the HasForeignKey method to inform the context about the non-conventional foreign key property, SamuraiFk. In doing so, be- cause Entrance is private in Samurai, I can’t use a lambda expression and am using an alternate syntax for the HasForeignKey parameters.
LastModifed is a shadow property—new to EF Core—and will get persisted into the database even though it’s not a property in the entities. The Ignore mapping is to ensure that the IsDirty prop- erty in Samurai isn’t persisted as it’s only for domain-relevant logic.
And that’s it. Given how much of the DDD patterns I’ve applied in my domain classes, there’s very little in the way of special map- pings that I have to add to the SamuraiContext class to inform EF Core 2.0 what the database looks like or how to store and retrieve data from that database. And I’m pretty impressed by that.
There’s No Such Thing as a Perfect DDD Sample
This is still a simple example because other than outputting “It’s a secret” when a SecretIdentity hasn’t been given a value, I’m not solving any complex problems in the logic. The subtitle of Eric Evan’s DDD book is “Tackling Complexity in the Heart of Software.” So much of the guidance regarding DDD is about breaking down overwhelming complex problems in to smaller solvable problems. The code design patterns are only a piece of that. Everyone has dif- ferent problems to solve in their domains and, often, readers ask for a sample that can be used as a template for their own software. But all that those of us who share our code and ideas can do is provide examples as learning tools. You can then extrapolate those lessons and apply some of the thinking and decision making to your own problems. I could spend even more time on this tiny bit of code and apply additional logic and patterns from the DDD arsenal, but this sample does go pretty far in leveraging DDD ideas to create a deeper focus on behavior rather than on properties, and further encapsulate and protect the aggregate.
My goal in these two columns was to show how EF Core 2.0 is so much friendlier for mapping your DDD-focused domain model to your database. While I demonstrated that, I hope you were also inspiredbytheDDDpatternsI’veincludedintheseclasses. n
Julie lerman is a Microsoft Regional Director, Microsoft MVP, software team men- tor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of “Programming Entity Framework,” as well as a Code First and a DbContext edition, all from O’Reilly Media. Follow her on Twitter: @julielerman and see her Pluralsight courses at juliel.me/PS-Videos.
Thanks to the following Microsoft technical expert for reviewing this article: Cesar de La Torre
public class SamuraiContext : DbContext {
public DbSet<Samurai> Samurais { get; set; }
protected override void OnConfiguring (DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlite ("Filename=DP0917Samurai.db"); }
protected override void OnModelCreating (ModelBuilder modelBuilder) { modelBuilder.Entity<Samurai> ()
.HasOne (typeof (Entrance), "Entrance")
.WithOne ().HasForeignKey(typeof (Entrance), "SamuraiFk");
foreach (var entityType in modelBuilder.Model.GetEntityTypes ()) { modelBuilder.Entity (entityType.Name).Property<DateTime>
("LastModified");
modelBuilder.Entity (entityType.Name).Ignore ("IsDirty");
}
modelBuilder.Entity<Samurai> ().OwnsOne (typeof (PersonFullName),
"SecretIdentity"); }
public override int SaveChanges () {
foreach (var entry in ChangeTracker.Entries ()
.Where (e => e.State == EntityState.Added || e.State == EntityState.Modified)) {
if (!(entry.Entity is PersonFullName))
entry.Property ("LastModified").CurrentValue = DateTime.Now;
}
return base.SaveChanges (); }
}
12 msdn magazine
Data Points


































































































   14   15   16   17   18