Page 16 - MSDN Magazine, September 2017
P. 16

because of some edge case, you’ll have to add a configuration, which I’ll demonstrate shortly when I rename the foreign key property.
To demonstrate a one-to-one relationship, I’ll use my favorite EF Core domain: classes from the movie “Seven Samurai”:
public class Samurai {
public int Id { get; set; }
public string Name { get; set; } public Entrance Entrance { get; set; }
}
public class Entrance {
public int Id { get; set; }
public string SceneName { get; set; } public int SamuraiId { get; set; }
}
Now with EF Core, this pair of classes—Samurai and Entrance (the character’s first appearance in the movie)—will be correctly identified as a uni-directional one-to-one relationship, with En- trance being the dependent type. I don’t need to include a naviga- tion property in the Entrance and I don’t need any special mapping in the Fluent API. The foreign key (SamuraiId) follows convention, so EF Core is able to recognize the relationship.
EF Core infers that in the database, Entrance.SamuraiId is a unique foreign key pointing back to Samurai. Keep in mind some- thing I struggled with because (as I have to continually remind myself), EF Core is not EF6! By default, .NET and EF Core will treat the Samurai.Entrance as an optional property at run time unless you have domain logic in place to enforce that Entrance is required. Starting with EF4.3, you had the benefit of the validation API that would respond to a [Required] annotation in the class or mapping. But there is no validation API (yet?) in EF Core to watch for that particular problem. And there are other requirements that are database-related. For example, Entrance.SamuraiId will be a non-nullable int. If you try to insert an Entrance without a SamuraiId value populated, EF Core won’t catch the invalid data, which also means that the InMemory provider currently doesn’t complain. But your relational database should throw an error for the constraint conflict.
From a DDD perspective, however, this isn’t really a problem because you shouldn’t be relying on the persistence layer to point out errors in your domain logic. If the Samurai requires an Entrance, that’s a business rule. If you can’t have orphaned Entrances, that’s also a business rule. So the validation should be part of your domain logic anyway.
For those edge cases I suggested earlier, here’s an example. If the foreign key in the dependent entity (for example, Entrance) doesn’t follow convention, you can use the Fluent API to let EF Core know. If Entrance.SamuraiId was, perhaps Entrance.SamuraiFK, you can clarify that FK via:
modelBuilder.Entity<Samurai>().HasOne(s=>s.Entrance) .WithOne().HasForeignKey<Entrance>(e=>e.SamuraiFK);
If the relationship is required on both ends (that is, Entrance must have a Samurai) you can add IsRequired after WithOne.
Properties Can Be Further Encapsulated
DDD guides you to build aggregates (object graphs), where the aggregate root (the primary object in the graph) is in control of all of the other objects in the graph. That means writing code that prevents other code from misusing or even abusing the rules.
Encapsulating properties so they can’t be randomly set (and, often, randomly read) is a key method of protecting a graph. In EF6 and earlier, it was always possible to make scalar and navigation properties have private setters and still be recognized by EF when it read and updated data, but you couldn’t easily make the prop- erties private. A post by Rowan Miller shows one way to do it in EF6 and links back to some earlier workarounds (bit.ly/2eHTm2t). And there was no true way to protect a navigation collection in a one-to-many relationship. Much has been written about this latter problem. Now, not only can you easily have EF Core work with private properties that have backing fields (or inferred backing fields), but you can also truly encapsulate collection properties, thanks to support for mapping IEnumerable<T>. I wrote about the backing fields and IEnumerable<T> in my previously mentioned January 2017 column, so I won’t rehash the details here. However, this is very important to DDD patterns and therefore relevant to note in this article.
While you can hide scalars and collections, there’s one other type of property you may very well want to encapsulate— navigation properties. Navigation collections benefit from the IEnumerable<T> support, but navigation properties that are private, such as Samurai.Entrance, can’t be comprehended by the model. However, there is a way to configure the model to comprehend a navigation property that’s hidden in the aggregate root.
For example, in the following code I declared Entrance as a pri- vate property of Samurai (and I’m not even using an explicit backing field, though I could if needed). You can create a new Entrance with the CreateEntrance method (which calls a factory method in Entrance) and you can only read an Entrance’s SceneName prop- erty. Note that I’m employing the C# 6 null-conditional operator to prevent an exception if I haven’t yet loaded the Entrance:
private Entrance Entrance {get;set;}
public void CreateEntrance (string sceneName) {
Entrance = Entrance.Create (sceneName); }
public string EntranceScene => Entrance?.SceneName;
By convention, EF Core won’t make a presumption about this private property. Even if I had the backing field, the private Entrance wouldn’t be automatically discovered and you wouldn’t be able to use it when interacting with the data store. This is an intentional API design to help protect you from potential side effects. But you can configure it explicitly. Remember that when Entrance is public, EF Core is able to comprehend the one-to-one relationship. However, because it’s private you first need to be sure that EF knows about this.
Figure 1 The PersonName Value Object
public class PersonName : ValueObject<PersonName> {
public static PersonName Create (string first, string last) {
return new PersonName (first, last); }
private PersonName () { }
private PersonName (string first, string last) {
First = first;
Last = last; }
public string First { get; private set; } public string Last { get; private set; } public string FullName => First + " " + Last;
}
12 msdn magazine
Data Points





























































   14   15   16   17   18