Reputation: 1462
I have an aggregate root in my domain model named Employee. it has 3 list of different domain objects like below:
public class Employee: EntityBase, IAggregateRoot
{
public long PersonnelCode { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string NationalCode { get; private set; }
public DateTime Birthdate { get; private set; }
public string Password { get; private set; }
public string Address { get; private set; }
public IReadOnlyList<Contract> Contracts => _Contracts.AsReadOnly();
public IReadOnlyList<EmployeeShiftAssignment> EmployeeShiftAssignments => _ShiftAssignments.AsReadOnly();
public IReadOnlyList<OverallWorkSummary> OverallWorkSummaries => _OverallWorkSummaries.AsReadOnly();
private List<Contract> _Contracts = new List<Contract>();
private List<EmployeeShiftAssignment> _ShiftAssignments = new List<EmployeeShiftAssignment>();
private List<OverallWorkSummary> _OverallWorkSummaries=new List<OverallWorkSummary>();
}
this is the implementation of each object:
public class Contract:EntityBase
{
public Contract(Guid employeeId,DateTime startDate,DateTime endDate)
{
this.EmployeeId = employeeId;
}
public Guid EmployeeId { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
}
public class EmployeeShiftAssignment:ValueObject<EmployeeShiftAssignment>
{
protected EmployeeShiftAssignment() { }
public EmployeeShiftAssignment(Guid shiftId,DateTime assignmentDate,bool archived=false)
{
this.ShiftId = shiftId;
this.AssignmentDate = assignmentDate;
}
public Guid ShiftId { get; private set; }
public DateTime AssignmentDate { get; private set; }
public bool Archived { get;private set; }
}
public class OverallWorkSummary : ValueObject<OverallWorkSummary>
{
protected OverallWorkSummary() { }
public OverallWorkSummary(
DateTime startDate,
DateTime endDate,
double totalWorkInHours,
double totalOvertimeInHours,
double totalUndertimeInHours
)
{
StartDate = startDate;
EndDate = endDate;
TotalWorkInHours = totalWorkInHours;
TotalOvertimeInHours = totalOvertimeInHours;
TotalUndertimeInHours = totalUndertimeInHours;
}
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
public double TotalWorkInHours { get; private set; }
public double TotalOvertimeInHours { get; private set; }
public double TotalUndertimeInHours { get; private set; }
}
and this is Employee Mapping configuration:
public class EmployeeMapping : EntityMappingBase<Employee>, IEntityMapping
{
public override void Configure(EntityTypeBuilder<Employee> builder)
{
Initial(builder);
builder.Property(i => i.FirstName)
.IsRequired()
.HasMaxLength(50);
builder.Property(i => i.LastName)
.IsRequired()
.HasMaxLength(100);
builder.Property(i => i.NationalCode)
.IsRequired()
.HasMaxLength(10);
builder.Property(i => i.Address)
.IsRequired();
builder.Property(i => i.Birthdate)
.IsRequired()
.HasColumnType(nameof(SqlDbType.Date));
builder.Property(i => i.Password)
.IsRequired()
.HasMaxLength(16);
builder.Property(i => i.PersonnelCode)
.IsRequired();
builder.OwnsMany(i => i.EmployeeShiftAssignments, map =>
{
map.ToTable("EmployeeShiftAssignments", "EmployeeContext").HasKey("Id");
map.Property<long>("Id").ValueGeneratedOnAdd();
map.WithOwner().HasForeignKey("EmployeeId");
map.Property(i=>i.AssignmentDate).IsRequired().HasColumnType(nameof(SqlDbType.Date));
map.Property(i => i.Archived).IsRequired();
map.UsePropertyAccessMode(PropertyAccessMode.Field);
});
builder.OwnsMany(i => i.OverallWorkSummaries, map =>
{
map.ToTable("OverallWorkSummaries", "EmployeeContext").HasKey("Id");
map.Property<long?>("Id").ValueGeneratedOnAdd();
map.WithOwner().HasForeignKey("EmployeeId");
map.Property(i => i.EndDate).IsRequired();
map.Property(i => i.StartDate).IsRequired();
map.Property(i => i.TotalWorkInHours).IsRequired();
map.Property(i => i.TotalUndertimeInHours).IsRequired();
map.Property(i => i.TotalOvertimeInHours).IsRequired();
map.UsePropertyAccessMode(PropertyAccessMode.Field);
});
}
}
public class ContractMapping : EntityMappingBase<Contract>,IEntityMapping
{
public override void Configure(EntityTypeBuilder<Contract> builder)
{
Initial(builder);
builder.Property(i => i.StartDate).IsRequired().HasColumnType(nameof(SqlDbType.Date));
builder.Property(i => i.EndDate).IsRequired().HasColumnType(nameof(SqlDbType.Date));
builder.HasOne<Employee>()
.WithMany(i => i.Contracts)
.HasForeignKey(i => i.EmployeeId)
.HasConstraintName("FK_Employee_Contracts");
}
}
and just in case you get curious:
public abstract class EntityMappingBase<TEntity> : IEntityTypeConfiguration<TEntity>,IEntityMapping
where TEntity:EntityBase
{
public abstract void Configure(EntityTypeBuilder<TEntity> builder);
protected void Initial(EntityTypeBuilder<TEntity> builder)
{
builder.Property(i => i.Id)
.HasColumnType(nameof(SqlDbType.UniqueIdentifier))
.IsRequired()
.ValueGeneratedNever();
builder.HasKey(i => i.Id);
builder.ToTable(typeof(TEntity).Name, typeof(TEntity).Namespace?.Split('.')[1]);
}
}
with this configuration whenever I try to read employee it throws an exception saying that 'Collection is readonly' without specifying which Collection!.
when I change IReadOnlyList to List error is gone but I can't do it because due to conventions we have we can't expose aggregate's internal members to outside of it.
this is my repository where I get error
public class EmployeeRepository : RepositoryBase<Employee>, IEmployeeRepository
{
public EmployeeRepository(IDbContext context) : base(context) { }
public Employee FindById(Guid Id)
{
return dbContext.Set<Employee>()
.Include(i => i.Contracts)
.FirstOrDefault(i => i.Id == Id);//Throws Exception Here!
}
public List<Employee> GetAll()
{
var e = dbContext.Set<Employee>()
.Include(i => i.Contracts)
.ToList();//Throws Exception Here!
return e;
}
}
what is causing this exception?
Upvotes: 1
Views: 1501
Reputation: 1462
Error was because of my invalid backing field naming.
By convention, the following fields will be discovered as backing fields for a given property (listed in precedence order).
_<camel-cased property name>
_<property name>
m_<camel-cased property name>
m_<property name>
In my domain object I have a field named which is not valid based on convention because it refers to <_ShiftAssignments> .
I renamed it to _EmployeeShiftAssignments and error was gone :)
Upvotes: 2