I'm practicing DDD, and I have a class which is a so called ValueObject
. Inside an Entity
I have custom list of that ValueObject
. I'm trying to persist that collection as JSON.
I have the following classes:
public class User : Entity
{
public string Username {get;set;}
private string _phoneNumbers = string.Empty;
public virtual PhoneList PhoneNumbers
{
get => (PhoneList)_phoneNumbers;
set => _phoneNumbers = value;
}
}
// A class to map the IList of PhoneNumber to string and vice versa
public class PhoneList : IEnumerable<PhoneNumber>
{
private IList<PhoneNumber> PhoneNumbers{ get; }
public PhoneList(IList<PhoneNumber> phoneNumbers)
{
PhoneNumbers = phoneNumbers;
}
public static explicit operator PhoneList(string phoneList)
{
var phoneNumbers = JsonConvert.DeserializeObject<IList<PhoneNumber>>(phoneList);
return new PhoneList(phoneNumbers);
}
public static implicit operator string(PhoneList phoneList)
{
return JsonConvert.SerializeObject(phoneList);
}
public IEnumerator<PhoneNumber> GetEnumerator()
{
return PhoneNumbers.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class PhoneNumber : ValueObject
{
public string Number {get;set;}
protected override IEnumerable<object> GetAtomicValues()
{
yield return Number;
}
}
When I run the EF Core migration tool with command: Add-Migration InitialCreate
Then I get the following error:
Error: The entity type 'PhoneNumber' requires a primary key to be defined.
The whole point is that I don't want to add a primary key to PhoneNumber
, because it's a ValueObject
.
I also don't understand why throws an error in migrations.
How can I fix this?
PS. I got this idea from this site
The migration is throwing an error because EF is trying to map your property User.PhoneNumbers
. You have to tell EF to ignore that property and to map instead your string field User._phoneNumbers
.
To do this you can use annotations or the fluent API. Check this page for examples of both.
With fluent API:
class MyDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Ignore(u => u.PhoneNumbers) //Do not map property
.Property("_phoneNumbers"); //Map field
...
}
}
If the data type of PhoneNumbers
and _phoneNumbers
was the same you wouldn't need to do anything as EF would implicitly deal with the private field as a backing field for the public property based on naming conventions, but this is not your case, as the data types are different. More info about backing fields in EF here.
Because of this also you may get an error when trying to map the _phoneNumbers
field, because there is already a property with name PhoneNumbers
but it doesn't have the expected type of string. In that case you should rename your field or your property to make sure they don't follow the backing fields naming convention (one is not a camelcase version of the other with '_' prefix).
By explicitly ignoring the property your error should go away. EF only maps entities that have a DbSet
in the DbContext
, that are used in navigation properties in other mapped entities, or that are included in OnModelCreating
method. Anyway, if you still get migration errors with the ValueObject
entities you can ignore the entities explicitly in OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Do not map these entities
modelBuilder.Ignore<PhoneNumber>();
modelBuilder.Ignore<PhoneList>();
...
}
More info about how EF decides which entities to map by convention here.
The whole point is that I don't want to add a primary key to PhoneNumber, because it's a ValueObject.
The PhoneNumber
is indeed a Value object
but only in the context of the Domain layer. From the Persistence point of view, the PhoneNumber
may be an Entity
and this is not against the DDD. If you see it as a Value object
then you are following the DDD approach. The Persistence is another beast that is fought with other weapons.
What I'm saying is based on the book by Vaughn Vernon, "Implementing DDD". Here is a paragraph from a relevant chapter:
There are times, however, when a Value Object in the model will of necessity be stored as an Entity with respect to a relational persistence store. In other words, when persisted, an instance of a specific Value Object type will occupy its own row in a relational database table that exists specifically for its type, and it will have its own database primary key column. This happens, for example, when supporting a collection of Value Object instances with ORM. In such cases the Value type persistent data is modeled as a database entity.