Page 16 - MSDN Magazine, November 2019
P. 16

I have a PersonFullName type with two string properties (First and Last) and no key property. This type also has other attributes of a value object. You can see its code in the accompanying down- load. And you’ll find a short primer on value objects in my column at msdn.com/magazine/mt846463.
The PersonFullName value object is used as a property of Employee:
public PersonFullName Name { get; private set; }
For EF Core to persist the value object along with the employee, I need to provide an explicit mapping in the DbContext:
modelBuilder.Entity<Employee>().OwnsOne(e=>e.Name);
All of this was available beginning with EF Core 2.0. A minor but handy fix that arrived in a later version of EF Core is related to constructors. EF Core can instantiate entities if there’s a constructor whose parameters match the properties of the entity. But owned entities didn’t allow this. However, that’s now been fixed, which is especially nice for value objects, as they need their property values at instantiation to avoid creating objects in an invalid state.
EF Core 3.0 Fix for a Significant Owned Entities Problem
There was a big flaw in the original behavior of owned entities that has now been fixed in EF Core 3.0. By default, EF Core maps the values of the owned entity to columns in the same table where the owner data is stored. But EF Core required that an owned property (for example, Employee.Name) be populated so that it could read its values (such as First and Last). This meant you could never leave the Name property null even if your business rules allowed it. With the help of some of the EF team members, I came up with a work around that I wrote about in the previously mentioned article.
This problem was not only tied to owned entities. It also affected related entities where the mappings forced a dependent entity’s data to be stored in the principal entity’s table.
A change in EF Core 3.0 fixes this problem. Entities that are owned or are dependents in a one-to-one relationship are now optional. In the case of my PersonFullName value object, if I don’t yet know the name of the new employee (for some strange reason), I can still cre- ate and persist an employee object.
Another issue in an earlier version of EF Core that has been corrected was that EF Core didn’t support updating or replacing owned entity properties. But this is a critical pattern to support because value objects are immutable and the owned entity map- ping is used for value objects, so the only way to “edit” the owned property is to replace it with a new instance. The issue was fixed
Figure 1 Override SaveChanges as a Temporary Workaround for a Problem with Replacing Null Owned Entities
in a prior update to EF Core and now the following code (which retrieves an entity, replaces its owned Name property, then saves) will persist correctly:
var employee = context.Employees.FirstOrDefault(); employee.Name=PersonFullName.Create("Diego", "Vega"); context.SaveChanges();
Temporary Workaround for a New Problem There is, unfor- tunately, a problem arising from the combination of these two fixes: updating owned entities that were originally null. The problem is known (as per this GitHub issue: bit.ly/2maQChH) and is targeted to be fixed in EF Core 3.1.0. In the meantime, if you want to update a null owned entity, Andriy Svyryd from the EF team provides a great workaround (which I’ve tested in a variety of scenarios) in the referenced GitHub issue. The workaround checks to see if the owner (for example, the employee) is also being added. If so, then Added is the correct state for the owned entity, as well. But if not, then the owned entity can’t be new and must therefore have been changed and its state should be marked as Modified.
The workaround takes place in the DbContext SaveChanges override, so your business logic doesn’t need to be aware of the temporary fix or that there’s even a problem. And when the new fix arrives, you’ll only need to remove the extra code in DbCon- text and won’t have to change anything in your business logic. The workaround leverages a newly exposed method called FindOwn- ership that will return detailed information about an owned entity. I modified the workaround a bit using the IsOwned method and my version is shown in Figure 1.
EF Core Keeps Improving Mappings for DDD
Each iteration of EF Core has brought improvements that enable it to more easily map our nicely designed DDD-influenced domain models. While there haven’t been a lot of new features added to EF Core 3.0, a lot of work was done to tighten up the APIs. The team thinks of this as a “foundational” version as it sets the stage for the continued enhancement of EF Core. They’re very open to ideas for improvements related to mapping domain models in order to minimize the need to build explicit data models when EF Core’s mappings aren’t sufficient to the task.
I hope these columns have helped you explore new technolo- gies, expand your knowledge and further your careers! Feel free to reach out to me via my blog at thedatafarm.com. If you want to keep up with me, I send out an occasional newsletter to share articles, upcomingconferences,videosandtrainingopportunities.Youcan find information about that on my Web site, as well. Thank you for the wonderful opportunity that this column has given me to learn and share with you. n
Julie lerman is a Microsoft Regional Director, Microsoft MVP, Docker Captain and software team coach 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 bit.ly/PS-Julie.
Thanks to the following Microsoft technical expert for reviewing this article: Diego Vega
public override int SaveChanges () {
foreach (var entry in ChangeTracker.Entries ()
.Where (e => e.Metadata.IsOwned () && e.State == EntityState.Added)) { var ownership = entry.Metadata.FindOwnership ();
var parentKey = ownership.Properties
.Select (p => entry.Property (p.Name).CurrentValue).
ToArray ();
var parent = this.Find (ownership.PrincipalEntityType.ClrType, parentKey); if (parent != null) {
var parentEntry = this.Entry (parent);
if (parentEntry.State != EntityState.Added) {
entry.State = EntityState.Modified; }
}
return base.SaveChanges (); }
12 msdn magazine
Data Points


































































































   14   15   16   17   18