Lets say I have some classes:
public class BaseModel
{
[Key]
public int Id { get; set; }
}
public class Person : BaseModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string Email { get; set; }
}
public class Employee : Person
{
public string Position { get; set; }
public decimal Wage { get; set; }
public PaymentType PaymentType { get; set; }
public virtual Company Company { get; set; }
}
Currently I have this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Employee>().HasRequired(e => e.PaymentType);
modelBuilder.Entity<Employee>().Map(t =>
{
t.MapInheritedProperties();
t.ToTable("Employees");
});
modelBuilder.Entity<Company>().HasMany(c => c.Employees).WithRequired(e => e.Company).Map(t => t.MapKey("Company_Id"));
}
I get two tables for Person and Employee, but I don't like what MapInheritedProperties() does by adding the Person properties to the Employee table.
How do I make the base class(Person) a foreign key?
In order to use the base class as a foreing key / navigational property without primary key problems. You need to be using Table per Type or Table per Hierarchy.
In your case using that modelBuilder should do it.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Employee>().HasRequired(e => e.PaymentType);
modelBuilder.Entity<Person >().ToTable("Persons");
modelBuilder.Entity<Employee>().ToTable("Employees");
modelBuilder.Entity<Company>().HasMany(c => c.Employees).WithRequired(e => e.Company).Map(t => t.MapKey("Company_Id"));
}
With this two table will be created. On names Persons will all fields for a person and one "Employees" for all fields for an employee. Both table will share the same primary key
You can get a really detailed explaination on Mortenza Manavi's blog
Related
I have an employee, with one hourly paying job, each hourly has multiple timecards. I would like the timecards to link to both the employee and Hourly.
public class Employee
{
public int Id { get; set; }
}
public class Hourly
{
public int EmployeeId { get; set; }
public List<Timecard> Timecards{ get; set; }
}
public class Hourly
{
public int HourlyId{ get; set; }
public int EmployeeId { get; set; }
}
How do I specify this relationship in EF.
The code appears to set the employeeID but causes issues with the migration and the Hourly is now set to null.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Timecard>()
.HasOne<HourlyPC>()
.WithMany(pc => pc.Timecards)
.HasForeignKey(t => t.EmployeeId)
.HasPrincipalKey(pc => pc.EmployeeId);
}
Violates 3NF, meaning duplicated data that can lead to problems such as data anomalies. One hack would be to include the Employee FK in a composite PK for Job. That way, when a Timecard has a FK to Job, it also has a FK to Employee. Perhaps you could use a job code for a second field for inclusion in the composite Job PK or reference another entity, an example of which is below where Position is the normalized details of a Job w/o employee specific data (e.g. hourly rate) and Job relates an Employee to a Position with the employee-specific details:
public class Employee
{
public int Id { get; set; }
}
public class Job
{
public int EmployeeId { get; set; }
public Employee Employee { get; set; }
public string PositionId { get; set; }
public Position Position { get; set; }
public ICollection<TimeCard> TimeCards { get; set; }
public decimal HourlyRate { get; set; }
}
public class TimeCard
{
public Id { get; set; }
public int EmployeeId { get; set; }
public Employee Employee { get; set; }
public string PositionId { get; set; }
public Job Job { get; set; }
}
Config:
// configure Job
// configure relationshipt to Position
modelBuilder.Entity<Job>()
.HasOne(j => j.Position)
.WithMany()
.IsRequired();
// configure relationship to Employee
modelBuilder.Entity<Job>()
.HasOne(j => j.Employee)
.WithMany()
.IsRequired();
// create composite PK using the two FK's
modelBuilder.Entity<Job>()
.HasKey(j => new { j.EmployeeId, j.PositionId });
// configure TimeCard
// configure nav prop to Employee
modelBuilder.Entity<TimeCard>()
.HasOne(tc => tc.Employee);
// configure relationship with Job
modelBuilder.Entity<TimeCard>()
.HasOne(tc => tc.Job)
.WithMany(j => j.TimeCards)
.HasForeignKey(tc => new { tc.EmployeeId, tc.PositionId })
.IsRequired();
That might need a bit of tweaking but that's the nuts and bolts of it.
I'm learning EF Core and the below is my three POCOs:
public class County
{
[Key]
public int cid { get; set; }
public string cname { get; set; }
}
public class City
{
[Key]
public int cid { get; set; }
public string cname { get; set; }
}
public class People
{
[Key]
public int pid { get; set; }
public string pname { get; set; }
public int cid { get; set; }
public City WhichCity { get; set; }
}
I'm expecting two foreign keys but only got one from City table. How to make it(using annotation or fluent API or whatever) except explicitly define a County variable to People class.
Just want to clarify: you don't need to have navigation properties, i.e., public City City { get; set; } in order to setup relationships. The only things you need are the foreign key and proper configurations.
I think the following configuration would work for you (not tested though):
Entities
Here I also purposely modified your existing classes to follow C# Naming Conventions, if you care. Remember, if you're doing Code First, that means you can have your classes however you want first. You think about persistence later on. Actually I will show you how you can rename classes' properties when you persist them to your database via Configurations.
public class County
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}
public class People
{
public int Id { get; set; }
public string Name { get; set; }
public int CityId { get; set; }
// Optional
//public City City { get; set; }
public int CountyId { get; set; }
// Optional
//public County County { get; set; }
}
Configurations
Instead of using Data Annotation, you can use Fluent API with configurations to configure how you want to map your classes back to database.
public class CountyConfiguration : IEntityTypeConfiguration<County>
{
public void Configure(EntityTypeBuilder<County> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("cid"); // If you want to rename to "cid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("cname"); // If you want to rename to "cname"
builder.ToTable("so_county"); // If you want to rename the table
}
}
public class CityConfiguration : IEntityTypeConfiguration<City>
{
public void Configure(EntityTypeBuilder<City> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("cid"); // If you want to rename to "cid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("cname"); // If you want to rename to "cname"
builder.ToTable("so_city"); // If you want to rename the table
}
}
public class PeopleConfiguration : IEntityTypeConfiguration<People>
{
public void Configure(EntityTypeBuilder<People> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("pid"); // If you want to rename to "pid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("pname"); // If you want to rename to "pname"
// Relationship
builder.HasOne<County>() // People has one County
.WithMany() // County has many people
.HasForeignKey<County>(x => x.CountyId); // Foreign key is CountyId
builder.HasOne<City>() // People has one City
.WithMany() // City has many people
.HasForeignKey<City>(x => x.CityId); // Foreign key is CityId
builder.ToTable("so_people"); // If you want to rename the table
}
}
And lastly, you need to apply those configurations OnModelCreating:
public class YourDbContext : DbContext
{
public DbSet<County> Counties { get; set; }
public DbSet<City> Cities { get; set; }
public DbSet<People> People { get; set; }
public YourDbContext(DbContextOptions<YourDbContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new CountyConfiguration());
builder.ApplyConfiguration(new CityConfiguration());
builder.ApplyConfiguration(new PeopleConfiguration());
}
}
DISCLAIM: wrote it by hand. Not tested.
I have the following situation: I have a class Company, it is as simple as possible
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
}
I also need to extend the IdentityUser class from ASP. NET Identity Core:
public class User : IdentityUser
{
public Company Company { get; set; }
public int CompanyID { get; set; }
}
Not bad so far. But I also need a class Device :
public class Device
{
public Company Company { get; set; }
public int CompanyID { get; set; }
}
Accordingly, we add the Devices property of the List<Device> type to the Company class and Users property of the List<User> type.
The problem is that I want to separate the data (Device table) from the data of ASP .NET Identity Core in different databases. Table Company I want to put in the database with Auth data. And table Device in other one. How can I tell the Entity Framework that the Company property of the Device class is from another database?
If the Company property is from another database, you may need to add a second database context for it, if you haven't already. Something like this:
public class CompanyModelContext : DbContext
{
public CompanyModelContext(DbContextOptions options) : base(options){ }
public DbSet<Company> Company { get; set; }
}
You can try to set your EF model like this:
Companies table:
[Table("Companies")]
public class Company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Device> Devices { get; set; }
}
Devices table:
[Table("Devices")]
public class Device
{
[Key]
public int Id { get; set; }
public Company Company { get; set; }
public int CompanyID { get; set; }
}
ApplicationDbContext class:
public DbSet<Company> Companies { get; set; }
public DbSet<Device> Devices { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Company>(buildAction =>
{
buildAction.ToTable("Companies");
buildAction.HasMany(x => x.Devices)
.WithOne(x => x.Company)
.OnDelete(DeleteBehavior.Cascade);
};
}
I've added Devices as a list in Company class, then overriding the FK in the method OnModelCreating.
Once you delete an item in table Company, all items with the same Id in table Devices (comparing to CompanyID) will be delted automatically.
Since we use the 2 properties [Table] and [Key] to define table name and the primary key. So in the OnModelCreating, you can ignore to define the name, PK, FK of table Device.
Company has many User
In this case:
User class:
public class User : IdentityUser
{
public int CompanyId { get; set; }
public Company Company { get; set; }
}
Company class:
[Table("Companies")]
public class Company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Device> Devices { get; set; }
public ICollection<User> Users { get; set; }
}
ApplicationDbContext class:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Company>(buildAction =>
{
buildAction.ToTable("Companies");
buildAction.HasMany(x => x.Devices)
.WithOne(x => x.Company)
.OnDelete(DeleteBehavior.Cascade);
buildAction.HasMany(x => x.Users)
.WithOne(x => x.Company)
.OnDelete(DeleteBehavior.Cascade);
};
}
Is it possible to create 2 M:M relationships using the same join table?
I have the following situation and am receiving the exception:
Unhandled Exception: System.InvalidOperationException: Cannot create a relationship between 'ApplicationUser.ExpertTags' and 'UserTag.User', because there already is a relationship between 'ApplicationUser.StudyTags' and 'UserTag.User'. Navigation properties can only participate in a single relationship
In Tag:
public class Tag {
public Tag() {
Users = new List<UserTag>();
}
public int TagId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<UserTag> Users { get; set; }
In ApplicationUser:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
StudyTags = new HashSet<UserTag>();
ExpertTags = new HashSet<UserTag>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public ICollection<UserTag> StudyTags { get; set; }
public ICollection<UserTag> ExpertTags { get; set; }
}
In UserTag (CLR join):
public class UserTag
{
public string UserId { get; set; }
public ApplicationUser User { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
In ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserTag>()
.HasKey(x => new { x.UserId, x.TagId });
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.StudyTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.ExpertTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.Tag)
.WithMany(t => t.Users)
.HasForeignKey(ut => ut.TagId);
}
Do I need to create separate CLR classes? Something like UserStudyTag and UserExpertTag?
Thanks!
Step down to SQL DB. You want to have table UserTag with one UserId field. How EF should guess, which records in this table are related to StudyTags and which to ExpertTags collections?
You should duplicate something.
Either split UserTag to two tables (UserStudyTag and UserExpertTag), or make two UserId fields in UserTag, say ExpertUserId and StudyUserId. Both nullable, with only one having some value in each record.
I have in my model Student that have a collection of all his subjects and every subject have collection of Educational matches.
public class Subject
{
public int SubjectID { get; set; }
public string SubjectName {get; set; }
public ICollection<Student> { get; set; }
}
public class EducationalMatches
{
public int EducationalMatchesID { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}
public class Student
{
public int StudentID { get; set; }
public Icollection<AllStudentSubjects> AllStudentSubjects{ get; set; }
}
public class AllStudentSubject
{
public int AllStudentSubjectID { get; set; }
public Subject Subject { get; set; }
public ICollection<EducationalMatches> Educations { get; set; }
}
I'm expecting that in DB a table that looks like that will appear:
tableID
StudentID
SubjectID
EducationMatchesID
but no such table appears.
anyone have an idea?
Having a model is not enough, you need to override OnModelCreating method (it is empty by default). Plus EF wants to have 'reverse' property, for example, if Student has a collection of Subjects, Subject should have a collection of Students (for many-to-many relationship)
In your case for AllStudentSubject it should be like this (did not test)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AllStudentSubject>()
.HasKey(a => a.AllStudentSubjectID ) //Primary key, I prefer [Key] attribute, but this also works
.HasRequired(a => a.Student) //Property in AllStudentSubject
.WithMany(s => s.AllStudentSubjects) // Collection property in Student class
.HasForeignKey(a => a.StudentId)//Second property in AllStudentSubject
//For Student, you do not have to write this all again, just the primary key
modelBuilder.Entity<Student>()
.HasKey(a => a.StudentId ) //I like to move 'simple' declarations like this to the top
}
For other two entities you have to do the same.
Here`s a great article with all concepts explained