How do I persist value objects in Entity Framework without polluting my domain model? EF (well, relational DBs in general) require me to define a key - which my value object
Vaughn Vernon writes about Persisting Value Objects (page 248) in his excellent book Implementing Domain-Driven Design.
ORM and Single Value Objects
The basic idea is to store each of the attributes of the Value in separate columns of the row where its parent Entity is stored. Said another way, a single Value Object is denormalized into its parent Entity's row. There are advantages to employing convention for column naming to clearly identity and standardize the way serialized objects are named.
ORM and Many Values Backed by a Database Entity
A very straightforward approach to persisting a collection of Value instances using an ORM and a relational database is to treat the Value type as an entity in the data model. (...) To accomplish this we can employ a Layer Supertype.
Sample bounded contexts in C# can be found here: https://github.com/VaughnVernon/IDDD_Samples_NET
I'm currently working through some of these same challenges. I'm not really a fan of adding an Id to your base ValueObject<T>
class since this gives an Id to all value objects whether or not they are needed plus as a value object by definition has no Id, it's anything inheriting that base type will no longer be a value object in the pure sense.
Before I go further, I would note that a key concept in coding DDD is that you don't have to be pure DDD everywhere, just so long as you know what concessions your making and their trade-offs. That being said, your approach can certainly be considered fine, however I believe it's adds a concession that may not really necessary. Primarily, this impacts the equality of your value objects. With the addition of Id, two Tags, even with the same name are no longer equal.
Here are my approaches to this situation: First the easy one, not really applicable to what I think your problem is but it's important. This is the single value object in the first part of Martin's answer.
As long as your value object consists of just simple type properties, Entity Framework will map this just fine.
For Example:
public class BlogEntry : Entity<Guid>
{
public String Text { get; private set; }
public Tag Tag { get; private set; }
// Constructors, Factories, Methods, etc
}
Entity Framework will handle that just fine, what you will end up with is a single table BlogEntry that consists of simply of:
Now I figure that's not really what your after in this case, but for many value objects it works out great. One I use frequently is a DateRange value object which consists of several properties. Then on my domain objects I simply have a property of the Type DateRange. EF maps those to the table for the domain object itself.
I bring this up because going back to the concession we made of adding Id to the ValueObject<T>
base type, even though it Id may not be listed in your domain object concrete implementation, it is still there and will still get picked up by Entity Framework which for this, probably the most common value object use case no longer working quite as nicely.
OK, finally, onto your specific case (which I too have run into a few times). Here is how I have opted to handle the need for a entity to contain a list of Value Objects. Basically it boils down to expanding our understanding of the domain. Assuming the Tag value object is for recording Tag's in a blog post, the way I look at it is that a BlogPost contains a list of PostTag with the value of Tag. Yes it is one more class, but you don't need to add it for every value object, it's only needed when you have a list of value objects, and I think better expresses what is happening.
So here is an example of adding a list of a value object to an entity (using your value object of Tag above):
public class BlogEntry : Entity<Guid>
{
public String Text { get; private set; }
public ICollection<PostTag> PostTags { get; private set; }
// Constructors:
private BlogEntry(Guid id) : base(id) { }
protected BlogEntry() : this(Guid.NewGuid()) { }
// Factories:
public static BlogEntry Create (String text, ICollection<PostTag> tags = null)
{
if(tags == null) { tags = new List<PostTag>(); }
return new BlogEntry(){ Text = text, Tags = tags };
}
// Methods:
public void AddTag(String name)
{
PostTags.Add(PostTag.Create(name));
}
}
public class PostTag : Entity<Guid>
{
// Properties:
public Tag Tag { get; private set; }
public DateTime DateAdded { get; private set; } // Properties that aren't relevant to the value of Tag.
// Constructors:
private PostTag(Guid id) : base(id) { }
protected PostTag() : this(Guid.NewGuid()) { }
// Factories:
public static PostTag Create(Tag tag)
{
return new PostTag(){ Tag = tag, DateAdded = DateTime.Now };
}
public static PostTag Create(Tag tag, DateTime dateAdded)
{
return new PostTag(){ Tag = tag, DateAdded = dateAdded };
}
}
That will allow your BlogEntry to contain multiple tags without compromising value objects and Entity Framework will map it just fine without the need to do anything special.
I think it may be useful to know for those who use Entity Framework Core 2.0 that
Having no ID field in a class to be used by Entity Framework (EF) was not possible until EF Core 2.0, which greatly helps to implement better value objects with no ID.
Here is detailed information from Microsoft about this feature:
Persist value objects as owned entity types in EF Core 2.0 and later Even with some gaps between the canonical value object pattern in DDD and the owned entity type in EF Core, it's currently the best way to persist value objects with EF Core 2.0 and later.
The owned entity type feature was added to EF Core since version 2.0.
An owned entity type allows you to map types that do not have their own identity explicitly defined in the domain model and are used as properties, such as a value object, within any of your entities. An owned entity type shares the same CLR type with another entity type (that is, it's just a regular class). The entity containing the defining navigation is the owner entity. When querying the owner, the owned types are included by default.
Just by looking at the domain model, an owned type looks like it doesn't have any identity. However, under the covers, owned types do have identity, but the owner navigation property is part of this identity.
The identity of instances of owned types is not completely their own. It consists of three components:
-The identity of the owner
-The navigation property pointing to them
-In the case of collections of owned types, an independent component (supported in EF Core 2.2 and later).
By convention, a shadow primary key is created for the owned type and it will be mapped to the same table as the owner by using table splitting. This allows to use owned types similarly to how complex types are used in EF6 in the traditional .NET Framework.
It is important to note that owned types are never discovered by convention in EF Core, so you have to declare them explicitly.
Additional details on owned entity types
-Owned types are defined when you configure a navigation property to a particular type using the OwnsOne fluent API.
-The definition of an owned type in our metadata model is a composite of: the owner type, the navigation property, and the CLR type of the owned type.
-The identity (key) of an owned type instance in our stack is a composite of the identity of the owner type and the definition of the owned type.
Owned entities capabilities
-Owned types can reference other entities, either owned (nested owned types) or non-owned (regular reference navigation properties to other entities).
-You can map the same CLR type as different owned types in the same owner entity through separate navigation properties.
-Table splitting is set up by convention, but you can opt out by mapping the owned type to a different table using ToTable.
-Eager loading is performed automatically on owned types, that is, there's no need to call .Include() on the query.
-Can be configured with attribute [Owned], using EF Core 2.1 and later.
-Can handle collections of owned types (using version 2.2 and later).
Owned entities limitations
-You can't create a DbSet of an owned type (by design).
-You can't call ModelBuilder.Entity() on owned types (currently by design).
-No support for optional (that is, nullable) owned types that are mapped with the owner in the same table (that is, using table splitting). This is because mapping is done for each property, we don't have a separate sentinel for the null complex value as a whole.
-No inheritance-mapping support for owned types, but you should be able to map two leaf types of the same inheritance hierarchies as different owned types. EF Core will not reason about the fact that they are part of the same hierarchy.
Main differences with EF6's complex types
-Table splitting is optional, that is, they can optionally be mapped to a separate table and still be owned types.
-They can reference other entities (that is, they can act as the dependent side on relationships to other non-owned types).
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ffffd-cqrs-patterns/implement-value-objects