Page 11 - MSDN Magazine, April 2018
P. 11

Figure 1 PostalAddress ValueObject
Figure 3 The Migration for the SalesOrder Table, Including All of the Columns for PostalAddress Properties
public class PostalAddress : ValueObject<PostalAddress> {
public static PostalAddress Create (string street, string city, string region, string postalCode)
{
return new PostalAddress (street, city, region, postalCode);
}
private PostalAddress () { }
private PostalAddress (string street, string city, string region,
string postalCode)
{
Street = street;
City = city;
Region = region; PostalCode = postalCode;
}
public string Street { get; private set; } public string City { get; private set; } public string Region { get; private set; } public string PostalCode { get; private set; } public PostalAddress CopyOf ()
{
return new PostalAddress (Street, City, Region, PostalCode); }
}
migrationBuilder.CreateTable( name: "SalesOrders", columns: table => new
{
Id = table.Column(nullable: false) .Annotation("Sqlite:Autoincrement", true),
OrderDate = table.Column(nullable: false),
OrderTotal = table.Column(nullable: false), BillingAddress_City = table.Column(nullable: true), BillingAddress_PostalCode = table.Column(nullable: true), BillingAddress_Region = table.Column(nullable: true), BillingAddress_Street = table.Column(nullable: true), ShippingAddress_City = table.Column(nullable: true), ShippingAddress_PostalCode = table.Column(nullable: true), ShippingAddress_Region = table.Column(nullable: true), ShippingAddress_Street = table.Column(nullable: true)
}
PostalAddress inherits from a ValueObject base class created by Jimmy Bogard (bit.ly/2EpKydG). ValueObject provides some of the obligatory logic required of a value object. For example, it has an override of Object.Equals, which ensures that every property is compared. Keep in mind that it makes heavy use of reflection, which may impact performance in a production app.
Two other important features of my PostalAddress value object are that it has no identity key property and that its constructor forces the invariant rule that every property must be populated. However, for an owned entity to be able to map a type defined as a value object, the only rule is that it have no identity key of its
Figure 2 The SalesOrder Class Contains Properties That Are PostalAddress Types
own. An owned entity is not concerned with the other attributes of a value object.
With PostalAddress defined, I can now use it as the Shipping- Address and BillingAddress properties of my SalesOrder class (see Figure 2). They aren’t navigation properties to related data, but just more properties similar to the scalar Notes and OrderDate.
Owned Entities allow the mapping of value objects to the data store.
These addresses now live within the SalesOrder and can provide accurate information regardless of the current address of the person who placed the order. I will always know where that order went.
Mapping a Value Object as an EF Core Owned Entity
In earlier versions, EF could automatically recognize classes that should be mapped using a ComplexType by discovering that the class was used as a property of another entity and it had no key property. EF Core, however, can’t automatically infer owned entities. You must specify this in the DbContext Fluent API mappings in the OnModelCreating method using the new OwnsOne method to specify which property of that entity is the owned entity:
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<SalesOrder>().OwnsOne(s=>s.BillingAddress);
modelBuilder.Entity<SalesOrder>().OwnsOne(s=>s.ShippingAddress); }
I’ve used EF Core migrations to create a migration file describing the database to which my model maps. Figure 3 shows the section of the migration that represents the SalesOrder table. You can see that EF Core understood that the properties of PostalAddress for each of the two addresses are part of the SalesOrder. The column names are as per EF Core convention, although you can affect those with the Fluent API.
Additionally, as mentioned earlier, putting the addresses into the SalesOrder table is by convention, and my preference. This
public class SalesOrder {
public SalesOrder (DateTime orderDate, decimal orderTotal) {
OrderDate = orderDate;
OrderTotal = orderTotal;
Id = Guid.NewGuid (); }
private SalesOrder () { }
public Guid Id { get; private set; }
public DateTime OrderDate { get; private set; }
public decimal OrderTotal { get; private set; }
private PostalAddress _shippingAddress;
public PostalAddress ShippingAddress => _shippingAddress;
public void SetShippingAddress (PostalAddress shipping) {
_shippingAddress = shipping; }
private PostalAddress _billingAddress;
public PostalAddress BillingAddress => _billingAddress; public void CopyShippingAddressToBillingAddress ()
{
_billingAddress = _shippingAddress?.CopyOf (); }
public void SetBillingAddress (PostalAddress billing) {
_billingAddress = billing; }
}
msdnmagazine.com
April 2018 7


































































































   9   10   11   12   13