Wrong mapping configuration on TPH inheritance in Entity Framework? - c#

I tried to apply TPH inheritance strategy on my Domain with Code First and FluentApi.
I have the following domain model:
public abstract class Entity
{
public Guid Id { get; set; }
public byte [] RowVersion { get; set; }
}
public class Employee : Entity
{
#region POCO Fields
public string EmployeeId { get; set; }
//Complex Types
public PrimaryPersonalData PrimaryPersonalData { get; set; }
public ContactPersonalData ContactPersonalData { get; set; }
#endregion
#region Navigations
public ICollection<EmployeePosition> EmployeeCurrentPositions { get; set; }
public ICollection<EmployeeDegree> EmployeeCurrentDegrees { get; set; }
public ICollection<EmployeeRole> EmployeeCurrentRoles { get; set; }
public ICollection<Department> AdministrationDepartments { get; set; }
public Guid? FacultyId { get; set; }
public Faculty Faculty { get; set; }
public Guid? SectorId { get; set; }
public Sector Sector { get; set; }
public Guid? AcademicDepartmentId { get; set; }
public Department AcademicDepartment { get; set; }
#endregion
#region Constructors
public Employee()
{
EmployeeCurrentPositions = new List<EmployeePosition>();
EmployeeCurrentDegrees = new List<EmployeeDegree>();
EmployeeCurrentRoles = new List<EmployeeRole>();
AdministrationDepartments = new List<Department>();
PrimaryPersonalData = new PrimaryPersonalData();
ContactPersonalData = new ContactPersonalData();
}
#endregion
}
public class Teacher : Employee
{
public ICollection<Subject> ReadeableSubjects { get; set; }
public Teacher()
{
ReadeableSubjects = new List<Subject>();
}
}
And configurations goes separately :
public abstract class EntityConfiguration<T> : EntityTypeConfiguration<T>
where T : Entity
{
protected EntityConfiguration()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(p => p.RowVersion).IsRowVersion();
}
}
public class EmployeeConfiguration : EntityConfiguration<Employee>
{
public EmployeeConfiguration()
{
#region POCO Configuration
Property(p=>p.EmployeeId)
.IsRequired()
.IsConcurrencyToken(true);
#endregion
#region Mapping Configuration
HasMany(p => p.EmployeeCurrentPositions)
.WithMany(p => p.TakingEmployees)
.Map(p =>
{
p.MapLeftKey("EmployeeId");
p.MapRightKey("PositionId");
p.ToTable("EmployeePosition");
});
HasMany(p => p.EmployeeCurrentDegrees)
.WithMany(p => p.TakingEmployees)
.Map(p =>
{
p.MapLeftKey("EmployeeId");
p.MapRightKey("DegreeId");
p.ToTable("EmployeeDegree");
});
HasMany(p => p.EmployeeCurrentRoles)
.WithMany(p => p.TakingEmployees)
.Map(p =>
{
p.MapLeftKey("EmployeeId");
p.MapRightKey("RoleId");
p.ToTable("EmployeeRole");
});
#endregion
}
public class TeacherConfiguration : EntityConfiguration<Teacher>
{
// I suppose problem is hidden here. TeacherConfiguration inherits from
// EntityConfiguration and may miss configuration of Employee. Am I right?
public TeacherConfiguration()
{
HasMany(p => p.ReadeableSubjects).WithMany(p => p.ReadeableTeachers).Map(m =>
{
m.MapLeftKey("TeacherId");
m.MapRightKey("SubjectId");
m.ToTable("TeacherSubject");
});
}
}
And the last piece - Faculty class that consists of both Employees and Teachers.
public class Faculty : Entity
{
#region POCO Fields
public string Acronym { get; set; }
public string FacultyNameEn { get; set; }
public string FacultyNameRu { get; set; }
public string FacultyDescriptionEn { get; set; }
public string FacultyDescriptionRu { get; set; }
#endregion
#region Navigations
public ICollection<Department> Departments { get; set; }
public ICollection<Employee> Employees { get; set; }
public ICollection<Teacher> Teachers { get; set; }
public ICollection<Student> Students { get; set; }
public ICollection<Subject> Subjects { get; set; }
public ICollection<Specialty> Specialties { get; set; }
#endregion
#region Constructors
public Faculty(string facultyAcronym, string facultyNameEn, string facultyNameRu,
string facultyDescriptionEn, string facultyDescriptionRu)
: this()
{
Acronym = facultyAcronym;
FacultyNameEn = facultyNameEn;
FacultyNameRu = facultyNameRu;
FacultyDescriptionEn = facultyDescriptionEn;
FacultyDescriptionRu = facultyDescriptionRu;
}
public Faculty()
{
Departments = new List<Department>();
Employees = new List<Employee>();
Students = new List<Student>();
Subjects = new List<Subject>();
Specialties = new List<Specialty>();
Teachers = new List<Teacher>();
}
#endregion
}
public class FacultyConfiguration : EntityConfiguration<Faculty>
{
public FacultyConfiguration()
{
#region POCO Configurations
Property(p => p.Acronym)
.IsRequired()
.HasMaxLength(10);
Property(p => p.FacultyNameEn)
.IsRequired()
.HasMaxLength(100);
Property(p => p.FacultyNameRu)
.IsRequired()
.HasMaxLength(100);
Property(p => p.FacultyDescriptionEn)
.IsRequired()
.HasMaxLength(1000);
Property(p => p.FacultyDescriptionRu)
.IsRequired()
.HasMaxLength(1000);
#endregion
#region Mapping Configurations
HasMany(p => p.Departments)
.WithRequired(p => p.Faculty)
.HasForeignKey(p => p.FacultyId);
HasMany(p => p.Employees)
.WithOptional(p => p.Faculty)
.HasForeignKey(p => p.FacultyId);
HasMany(p => p.Teachers)
.WithOptional(p => p.Faculty)
.HasForeignKey(p => p.FacultyId);
HasMany(p => p.Students)
.WithRequired(p => p.Faculty)
.HasForeignKey(p => p.FacultyId);
HasMany(p => p.Subjects)
.WithRequired(p => p.Faculty)
.HasForeignKey(p => p.FacultyId);
HasMany(p => p.Specialties)
.WithRequired(p => p.Faculty)
.HasForeignKey(p => p.FacultyId);
#endregion
}
}
And finally I got an exception :
Additional information: The foreign key component 'FacultyId' is not a declared property on type 'Teacher'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
I found similar question here but there is a bit different situation. I think problem is that TeacherConfiguration inherit from EntityConfiguration but not from EmployeeConfiguration. Could you help me to find what is wrong here?

Related

Navigation mapping issues in EF Core

I'm trying to achieve the following layout:
User table (has link to user details)
User details table (holds links to various detail tables)
but am getting the following error:
System.InvalidOperationException: Cannot create a relationship between 'Address.ClientDetails' and 'ClientDetails.ResidentialAddress', because there already is a relationship between 'ClientDetails.PostalAddress' and 'Address.ClientDetails'. Navigation properties can only participate in a single relationship.
I understand this problem would occur if entity framework had no way to identify which address to link to each address - but i thought i take care of that by specifying 2 links in the model and then each key map in the mapping class. Any help would be great!
my Client model and mapping looks as follows:
public class Client : BaseEntity
{
public ClientDetails ApplicantDetails
{
get
{
return this.ClientDetails.SingleOrDefault(e => e.ClientType == Enums.ClientType.Applicant.ToString());
}
}
public ClientDetails SpouseDetails
{
get
{
return this.ClientDetails.SingleOrDefault(e => e.ClientType == Enums.ClientType.Spouse.ToString());
}
}
public ICollection<ClientDetails> ClientDetails { get; set; }
public ICollection<BankDetails> BankDetails { get; set; }
public ICollection<Expenses> Expenses { get; set; }
public ICollection<Obligation> Obligations { get; set; }
public ICollection<Budget> Budgets { get; set; }
public ICollection<Document.Document> Documents { get; set; }
public virtual Workflow.Workflow Workflow { get; set; }
Mapping
public class ClientMapping: IEntityTypeConfiguration<Entities.Client.Client>
{
public void Configure(EntityTypeBuilder<Entities.Client.Client> builder)
{
builder.ToTable("Client");
builder.HasKey(e => e.Id);
builder.HasMany(e => e.ClientDetails).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Documents).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasOne(e => e.Workflow).WithOne(e => e.Client).HasForeignKey<Entities.Workflow.Workflow>(e => e.ClientId);
builder.HasMany(e => e.Obligations).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Expenses).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Budgets).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.BankDetails).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.Ignore(e => e.ApplicantDetails);
builder.Ignore(e => e.SpouseDetails);
}
}
Client Details and mapping
public class ClientDetails
{
public int ClientId { get; set; }
public int PersonalDetailsId { get; set; }
public int EmployerId { get; set; }
public int ResidentialAddressId { get; set; }
public int PostalAddressId { get; set; }
public int IncomeId { get; set; }
public string ClientType { get; set; }
public virtual Client Client { get; set; }
public virtual PersonalDetails PersonalDetails { get; set; }
public virtual Employer Employer { get; set; }
public virtual Address ResidentialAddress { get; set; }
public virtual Address PostalAddress { get; set; }
public virtual Income Income { get; set; }
}
mapping
public class ClientDetailsMapping : IEntityTypeConfiguration<Entities.Client.ClientDetails>
{
public void Configure(EntityTypeBuilder<ClientDetails> builder)
{
builder.ToTable("ClientDetails");
builder.HasKey(e => new { e.IncomeId, e.PersonalDetailsId, e.ClientId, e.PostalAddressId, e.ResidentialAddressId } );
builder.HasOne(e => e.Income).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.IncomeId);
builder.HasOne(e => e.PostalAddress).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.PostalAddressId);
builder.HasOne(e => e.ResidentialAddress).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.ResidentialAddressId);
builder.HasOne(e => e.Employer).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.EmployerId);
builder.HasOne(e => e.PersonalDetails).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.PersonalDetailsId);
}
}
Can you try to delete the content of "WithOne"?
try this:
builder.HasOne(e => e.PostalAddress).WithOne().HasForeignKey<ClientDetails>(e => e.PostalAddressId);
builder.HasOne(e => e.ResidentialAddress).WithOne().HasForeignKey<ClientDetails>(e => e.ResidentialAddressId);
Found this post:
ef core - two one to one on one principal key
Ended up implementing solution 3, client details now has a collection of addresses (which have an address type linked), I then added an address getter on the client details to get the address I want at a later time. Everything seems to work correctly now.

EF Core 2.1 foreign key with different datatype

I have thes model classes:
public partial class InventoryCustomer : EntityBase, ICrudEntity<int>
{
public int Id { get; set; }
public int? SlsalesLevelTypeId { get; set; }
public long? SalesLevelId { get; set; }
public virtual Customer Customer { get; set; }
public virtual GroupOfCustomer GroupOfCustomer { get; set; }
}
public partial class Customer : EntityBase, IPkEntity<long>
{
public long Id { get; set; }
public virtual ICollection<InventoryCustomer> InventoryCustomer { get; set; }
}
public partial class GroupOfCustomer : EntityBase, ICrudEntity<int>
{
public int Id { get; set; }
public virtual ICollection<InventoryCustomer> InventoryCustomer { get; set; }
}
Property Id must remain an int at GroupsOfCustomer and long at Customer.
Depends of SlsalesLevelTypeId, SalesLevelId at InventoryCustomer references either on Customer or GroupOfCustomer.
Map class look like this:
public class InventoryCustomerMap : EntityTypeConfiguration<InventoryCustomer>
{
public override void Map(EntityTypeBuilder<InventoryCustomer> builder)
{
builder.ToTable("InventoryCustomers");
builder.HasKey(e => e.Id);
builder.Property(e => e.SalesLevelId).HasColumnName("SalesLevelID");
builder.HasOne(d => d.Customer)
.WithMany(p => p.InventoryCustomer)
.HasForeignKey(d => d.SalesLevelId)
.HasPrincipalKey(p => p.Id);
builder.HasOne(d => d.GroupOfCustomer)
.WithMany(p => p.InventoryCustomer)
.HasForeignKey(d => d.SalesLevelId)
.HasPrincipalKey(p => p.Id);
}
}
But when I run code, I get an error:

EF One to Many Relationship on Composite Key

I'm having issues using Entity Framework (6.3) to retrieve a child collection of entities where the relationship uses a composite key. In the example below I'm trying to get the Sprints associated with a Plan, but the Sprints child collection keeps coming back empty.
// Returns no sprints
var queryUsingSelect = await _dbContext
.Plans
.Select(p => new
{
p,
p.Sprints
})
.ToListAsync();
// Returns a plan without sprints
var queryUsingInclude = await _dbContext
.Plans
.Include(p => p.Sprints)
.ToListAsync();
// Returns me all sprints
var allSprints = await _dbContext
.Plans
.SelectMany(p => p.Sprints)
.ToListAsync();
In the last query I've tested it using SelectMany which does return Sprints, but really I need to be able to do it using Include. I'm having the same issue with another collection in the same project so it seems to be an issue with my approach in general. Note that I have lazy loading turned off to prevent accidental n+1 queries.
Here's a stripped down version of my code:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
}
public class Aspiration
{
public int AspirationId { get; set; }
public string Title { get; set; }
}
public class Plan
{
public Plan()
{
Sprints = new List<Sprint>();
}
public int UserId { get; set; }
public int AspirationId { get; set; }
public virtual User User { get; set; }
public virtual Aspiration Aspiration { get; set; }
public virtual ICollection<Sprint> Sprints { get; set; }
}
public class Sprint
{
public int SprintId { get; set; }
public int UserId { get; set; }
public int AspirationId { get; set; }
public virtual Plan Plan { get; set; }
public virtual User User { get; set; }
public virtual Aspiration Aspiration { get; set; }
}
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
Property(t => t.Name)
.HasMaxLength(100)
.IsRequired();
}
}
public class AspirationMap : EntityTypeConfiguration<Aspiration>
{
public AspirationMap()
{
Property(t => t.Title)
.HasMaxLength(100)
.IsRequired();
}
}
public class PlanMap : EntityTypeConfiguration<Plan>
{
public PlanMap()
{
HasKey(s => new { s.UserId, s.AspirationId });
HasRequired(s => s.User)
.WithMany()
.HasForeignKey(s => s.UserId);
HasRequired(s => s.Aspiration)
.WithMany()
.HasForeignKey(s => s.AspirationId);
}
}
public class SprintMap : EntityTypeConfiguration<Sprint>
{
public SprintMap()
{
HasRequired(s => s.User)
.WithMany()
.HasForeignKey(s => s.UserId);
HasRequired(s => s.Aspiration)
.WithMany()
.HasForeignKey(s => s.AspirationId);
HasRequired(s => s.Plan)
.WithMany(d => d.Sprints)
.HasForeignKey(s => new { s.AspirationId, s.UserId });
}
}
public class MyDbContext : DbContext
{
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(null);
}
public MyDbContext()
: base(DbConstants.ConnectionStringName)
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Aspiration> Aspirations { get; set; }
public DbSet<Plan> Plans { get; set; }
public DbSet<Sprint> Sprints { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder
.Map(new UserMap())
.Map(new AspirationMap())
.Map(new PlanMap())
.Map(new SprintMap())
;
}
}
Well, I see some errors in your mapping.
The FK of Plan in Sprint must have the same order of the PK of Plan. So replace this:
HasRequired(s => s.Plan)
.WithMany(d => d.Sprints)
.HasForeignKey(s => new { s.AspirationId,s.UserId });
for this:
HasRequired(s => s.Plan)
.WithMany(d => d.Sprints)
.HasForeignKey(s => new { s.UserId, s.AspirationId });
After making those changes, I tried to run your code and everything worked fine.

One or more validation errors were detected during model generation

I have a problem when selecting a User.
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap ()
{
ToTable("USERS");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("ID");
Property(e => e.Name).HasColumnName("NAME");
Property(e => e.Password).HasColumnName("PASSWORD");
Property(e => e.Date).HasColumnName("DATE");
Property(e => e.Token).HasColumnName("TOKEN");
Property(e => e.Active).HasColumnName("ACTIVE");
HasRequired(e => e.Company).WithMany().Map(e => e.MapKey("COMPANY_ID"));
HasMany(e => e.BranchesUsers).WithRequired().Map(e => e.MapKey("USER_ID"));
}
}
public class BranchMap : EntityTypeConfiguration<Branch>
{
public BranchMap ()
{
ToTable("BRANCHES");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("ID");
Property(e => e.Name).HasColumnName("NAME");
Property(e => e.Date).HasColumnName("DATE");
Property(e => e.Active).HasColumnName("ACTIVE");
HasRequired(e => e.Company).WithMany().Map(e => e.MapKey("COMPANY_ID"));
HasMany(e => e.UsersBranches).WithRequired().Map(e => e.MapKey("BRANCH_ID"));
}
}
public class UserBranchMap : EntityTypeConfiguration<UserBranch>
{
public UserBranchMap()
{
ToTable("USERS_BRANCHES");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("ID");
HasOptional(e => e.User).WithMany().Map(e => e.MapKey("USER_ID"));
HasOptional(e => e.Profile).WithMany().Map(e => e.MapKey("PROFILE_ID"));
HasOptional(e => e.Branch).WithMany().Map(e => e.MapKey("BRANCH_ID"));
HasOptional(e => e.Company).WithMany().Map(e => e.MapKey("COMPANY_ID"));
}
}
this is my model:
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public DateTime Date { get; set; }
public string Token { get; set; }
public Company Company { get; set; }
public List<UserBranch> BranchesUsers{ get; set; }
public bool Active{ get; set; }
}
public class Branch
{
public long Id { get; set; }
public Company Company{ get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public List<UserBranch> UsersBranches { get; set; }
public bool Active { get; set; }
}
public class UserBranch
{
public long Id { get; set; }
public User User { get; set; }
public Profile Profile { get; set; }
public Branch Branch { get; set; }
public Company Company { get; set; }
}
when I perform a simple select the user model I get this error:
One or more validation errors were detected during model generation:
USER_ID: Name: Each property name in a type must be unique. Property name 'USER_ID' is already defined.
BRANCH_ID: Name: Each property name in a type must be unique. Property name 'BRANCH_ID' is already defined.

EntityFramework Code First FluentAPI TPC

Here there are my domain entities:
public class Province
{
private ICollection<City> _cities;
public virtual ICollection<City> Cities
{
get { return _cities ?? (_cities = new HashSet<City>()); }
set { _cities = value; }
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual double Latitude { get; set; }
public virtual double Longitude { get; set; }
}
public class City
{
private Province _province;
public virtual Province Province
{
get { return _province ?? (_province = new Province()); }
set { _province = value; }
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Latitude { get; set; }
public virtual string Longitude { get; set; }
}
Mappings:
public class ProvinceMap : EntityTypeConfiguration<Province>
{
public ProvinceMap()
{
this.ToTable("Province");
this.HasKey(p => p.Id);
this.Property(x => x.Id).HasColumnName("Id");
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.Name).HasMaxLength(50).IsRequired();
this.Property(x => x.Latitude).IsRequired();
this.Property(x => x.Longitude).IsRequired();
//this.HasMany(x => x.Cities)
// .WithRequired(x => x.Province)
// .HasForeignKey(x => x.Id);
}
}
public class CityMap : EntityTypeConfiguration<City>
{
public CityMap()
{
this.ToTable("City");
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasColumnName("Id");
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.Name).HasMaxLength(50).IsRequired();
this.Property(x => x.Latitude).IsRequired();
this.Property(x => x.Longitude).IsRequired();
this.HasRequired(x => x.Province)
.WithMany(x => x.Cities)
.HasForeignKey(x => x.Id);
}
}
Context:
public class DataContext : DbContext
{
public DataContext(): base("DataContext")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DataContext, Configuration>("DataContext"));
}
public DbSet<Province> Provinces { get; set; }
public DbSet<City> Cities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ProvinceMap());
modelBuilder.Configurations.Add(new CityMap());
//base.OnModelCreating(modelBuilder);
}
}
When I run the 'update-database' command at the Nuget Package Console, I have an error:
Invalid multiplicity in the element Role "City_Province_Source" in connection "City_Province". Because the Dependent Role refers to the key properties, the upper bound of the multiplicity properties Dependent Role must be equal to "1".
Logically, you are trying to define a 1-to-many relationship. Because City cannot be in many Provinces, and one Province can have many Cities.
In this case, you don't necessarily need to specify HasRequired and WithMany in your mapping.
Remove the following code from CityMap
this.HasRequired(x => x.Province)
.WithMany(x => x.Cities)
.HasForeignKey(x => x.Id);
Having ICollection<City> in Province table, and a property type of Province in City table is enough to establish the relationship.
The output will be like this.

Categories

Resources