Closed suugbut closed 6 months ago
Hi @suugbut ,
I think it's there because certain packages like EF Core, which use reflection to instantiate objects, rely on these parameterless constructors. As you can see in the capture below, this is what happens when the parameterless constructor for Reminder
is removed👇
Thank you very much. I am still learning CA and wondering why your entity models looks so smart and complicated. Someone told me that entity models should be made dumb or simple.
This is mostly related to domain modeling rather than CA. @amantinband has a couple of videos on this topic in his YouTube channel, in addition to a comprehensive course about domain driven design. you can check them out if you are interested.
Dear @amantinband, I think I understood now why you need to add non-default parameterless constructor. It is actually unnecessary if we make our entity classes as dumb as possible (explained later at the bottom).
EF Core cannot instantiate entity model classes when it cannot find a matching constructor. For example, we define parameter constructors that do not conform to the conventions used by EF Core.
public sealed class Student
{
public int Id { get; init; }
public string Name { get; set; } = default!;
public bool IsActive { get; set; }
// non-matching parameter constructor
public Student(int idx, string name)
{
Id = idx; // does NOT match
Name = name; // does match
}
}
In this case, explicit parameterless constructor must be declared.
public sealed class Student
{
public int Id { get; init; }
public string Name { get; set; } = default!;
public bool IsActive { get; set; }
// non-matching parameter constructor
public Student(int idx, string name)
{
Id = idx; // does NOT match
Name = name; // does match
}
private Student() { } // <============ Mandatory
}
The above non-matching constructor is only useful for us, not for EF Core. We usually invoke the non-matching constructor in creation endpoint.
var student = new Student(Guid.NewGuid(),"Thomas"){IsActive=true};
db.Students.Add(student);
db.SaveChanges();
It is not necessary for a matching constructor to have parameters that cover all properties.
For example, IsActive
is not included in the matching constructor.
EF Core will invoke the matching constructor first and then populate the remaining properties.
public sealed class Student
{
public int Id { get; init; }
public string Name { get; set; } = default!;
public bool IsActive { get; set; }
// matching parameter constructor
public Student(int id, string name)
{
Id = id;
Name = name;
}
}
Most people suggest that the entity model classes should be made as dumb as possible as follows.
public sealed class Student
{
public int Id { get; init; }
public string Name { get; set; } = default!;
public bool IsActive { get; set; }
}
We usually set the primary key Id
as init
or private init
because it must be immutable after creation.
And let EF Core generate the Id
automatically.
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
Your base entity already has a matching constructor, so non-default parameterless constructor is no longer mandatory. https://github.com/amantinband/clean-architecture/blob/09605637026f5e05375969b4fedb6c797dabe736/src/CleanArchitecture.Domain/Common/Entity.cs#L22
hey @suugbut!
The reason why the entities are not "dumb" or "simple" follows the "rich domain models" principle from Domain-Driven Design, which basically means - put as much business logic inside the entities.
The motivation behind making the properties as private and readonly as possible also comes from Domain-Driven Design, where we want to limit access to the underlying data and focus on exposing behavior.
Once you follow this approach, your fields will often not have private setters, and a private paramaterless constructor will be needed to allow EF Core to populate the fields via reflection.
You can definitely follow Clean Architecture and use your implementation (I actually have a video coming out this Monday (29.1) about this topic). DDD practices aren't required, and I only chose a few principals for my template since I think it makes the systems better in the long run.
I noticed that your entities have private parameterless constructors. What are they for?