Page 14 - MSDN Magazine, November 2019
P. 14
Vega continues, “There are always going to be cases in which you need to exclude some field (or property) from the mapping, because the state in the field represents some calculated value that is cached in memory but doesn’t need to be persisted, but mapping to backing fields provides a superior default starting point than mapping to public properties.”
So the biggest change to backing fields in EF Core 3.0 is that EF will always map to backing fields in all cases rather than only when querying. Even with the exception of queries, there had been a case reported where the query wasn’t using the backing field—when the backing field was a navigation property. Here’s an example where an employee class has a navigation property to the employee’s company. If I edit the employee’s company using the navigation property, that should trigger some other behavior, in this case
sending a message, like so:
private Company _company; public Company Company
{
get { return _company; } set
{
_company = value;
SendEmployeeChangedCompany(); }
}
Again, it doesn’t make sense for that to happen if I’m just popu- lating the Company property using a query such as:
context.Employees.Include(e=>e.Company).FirstOrDefault();
With EF Core 3.0, rather than specifying this additional case for querying, the broader decision (explained earlier) was made to always read or write to a backing field if it exists.
That’s the new default. However, remember (or note if this is new to you) that you can fine-tune how a DbContext uses backing fields for properties in the ModelBuilder. Its UserPropertyAccess- Mode setting can be set to one of six enums: Field (the default), FieldDuringConstruction, PreferField, PreferFieldDuringCon- struction, PreferProperty and Property.
Because this is a breaking change, you can set the value back to the old default (PreferFieldDuringConstruction) or use your tests to see where the behavior is changing in order to align your code.
The other two changes to backing fields cover narrow cases. The first issue occurred because the naming convention for matching fields with properties only checked the name and had an order of precedence:
1. _<camel-cased property name> 2. _<property name>
3. m_<camel-cased property name> 4. m_<property name>
It was possible to match a field to the wrong property—possibly
even a property of the wrong type—if more than one field fit the
pattern. Here’s an exaggerated example:
private string _name;
public string Name => _name; private string m_name;
public string OwnerName => m_name;
Hopefully, your DDD practices and use of a ubiquitous language will ensure you never find yourself in this situation. However, in EF Core 3.0, if more than one backing field matches a property, it will throw a runtime exception, rather than quietly making what could be the wrong choice.
The real problem with the preceding code is that OwnerName and m_name don’t fit any convention and you have to map them together regardless. This will leave only one backing field choice for Name, and EF Core will be happy:
modelBuilder.Entity<Company>().Property(c=>c.OwnerName).HasField("m_name");
But if you neglect to map OwnerName to m_name, EF Core 3.0 will report the ambiguous naming issue.
The last change is for backing fields that don’t map to any property yet still need to be persisted. Imagine you have a field, _favoriteEmployee, in your class but no property. Yet you need that field value to get persisted for a reporting system to access:
private string _favoriteEmployee; public void SetFave(string fave) {
_favoriteEmployee=fave; }
The model still needs to associate the field with a property in order for this to be persisted. You can achieve this by configur- ing a property and mapping it with HasField to the backing field. For example:
modelBuilder.Entity<Company>() .Property<string>("FavoriteEmployee").HasField("_favoriteEmployee");
Interestingly, because you map inferred properties in the same manner as shadow properties, I mistakenly referred to them as shadow properties at first. But shadow properties are for data that’s only in the data store and has no representation—not even a field—in the class.
To avoid issues with ambiguity that developers had been en- countering and to allow for cleaner API code, starting with EF Core 3.0, the shadow property name must match the field name exactly. Otherwise it will throw a runtime exception:
modelBuilder.Entity<Company>() .Property<string>("_favoriteEmployee").HasField("_favoriteEmployee");
In a follow-up release, you’ll be able to configure field names that don’t match exactly.
Using backing fields to encapsulate properties also extends to navigation properties and properties that are mapped as owned entities (commonly used for value objects).
EF Core enables encapsulation of collections, thanks to it being able to comprehend IEnumerable properties. This was a big deal for DDD practitioners because there was no way to do this in EF6 or earlier versions. I wrote about it in an earlier column, EF Core 1.1: A Few of My Favorite Things (msdn.com/magazine/mt745093). Nothing has changed with EF Core 3.0 in this regard.
Important Changes to Owned Entities in EF Core 3.0
Owned entities (also called owned types) are the feature that allows you to map value objects in EF Core. Owned entities in EF Core were the result of re-thinking the complex type feature that’s been in EF since the beginning. The recipe for owned types in EF Core is as follows:
• The type has no key property of its own.
• The type is used as a property of one or more entities
(or even another owned entity).
• The DbContext contains an OwnsOne or OwnsMany
mapping that defines the property as an owned type for each entity where it’s used as a property.
10 msdn magazine
Data Points