So following this example:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
I have a Student class:
public class Student
{
public Student() { }
public int StudentId { get; set; }
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
and a Course class
public class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
They have Many-to-Many relationship which is configured this way:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasMany<Course>(s => s.Courses).WithMany(c => c.Students).Map(c =>
{
c.MapLeftKey("Student_id");
c.MapRightKey("Course_id");
c.ToTable("StudentAndCourse");
});
base.OnModelCreating(modelBuilder);
}
I have created the models and tables myself and map to them. So far so good. The problem is I do not need the collection of related Students to my courses. In other words when I get my courses they are coming with their related Students. I need the Students with the assigned Courses but when I get the courses through my repo, I need only them.
I tried to remove the Students collection from my Course class but was enable to fix the mapping. I am not experienced with EF and any help with working example will be greatly appreciated.
There is WithMany() method which does not require navigation property for related entities:
modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany()
.Map(c =>
{
c.MapLeftKey("Student_id");
c.MapRightKey("Course_id");
c.ToTable("StudentAndCourse");
});
Related
What is the effective way to resolve ambiguity of many-to-many relationships that point to the same entity either through annotations or fluent configuration? Given models such as:
public class Team
{
public int TeamId { get; set; }
public string Name { get; set; }
// Teams can be owned by multiple users
public List<User> Owners { get; set; }
// Teams can have multiple members
public List<User> Members { get; set; }
}
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
// User can own zero to many teams
public List<Team> Owners { get; set; }
// User can be a member of zero to many teams
public List<Team> Members { get; set; }
}
Scaffolding results in an error along the lines of "Unable to determine the relationship by navigation "Team.Owners" of type "List".
Is this something that can be effectively resolved by manually creating join entities such as TeamOwner and TeamMember or would EF Core still struggle with ambiguity?
Thanks for any help you can provide.
You have two Navigation properties on each entity, and EF doesn't have a convention to identify which goes with which. So you need to configure the model to explicitly relate the navigation properties. You'll also want to pick a descriptive name for the linking table. EG:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Team>()
.HasMany(t => t.Owners)
.WithMany(o => o.OwnerOf)
.UsingEntity(j => j.ToTable("TeamOwners"));
modelBuilder.Entity<Team>()
.HasMany(t => t.Members)
.WithMany(o => o.MemberOf)
.UsingEntity(j => j.ToTable("TeamMembers"));
base.OnModelCreating(modelBuilder);
}
You can relate the navigation properties with annotations, but can't name the linking table. eg
public class Team
{
public int TeamId { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(User.OwnerOf))]
public List<User> Owners { get; set; }
[InverseProperty(nameof(User.MemberOf))]
public List<User> Members { get; set; }
}
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
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
I have a question about defining Foreign Key in EF Code First Fluent API.
I have a scenario like this:
Two class Person and Car. In my scenario Car can have assign Person or not (one or zero relationship).
Code:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public Person Person { get; set; }
public int? PPPPP { get; set; }
}
class TestContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Car> Cars { get; set; }
public TestContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
In my sample I want to rename foreign key PersonId to PPPPP. In my mapping I say:
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
But my relationship is one to zero and I'm afraid I do mistake using WithMany method, but EF generate database with proper mappings, and everything works well.
Please say if I'm wrong in my Fluent API code or it's good way to do like now is done.
Thanks for help.
I do not see a problem with the use of fluent API here. If you do not want the collection navigational property(ie: Cars) on the Person class you can use the argument less WithMany method.
Confusing Situation
I have a situation where I have 2 entities where 1 inherits from the other, that need to map to 2 separate tables, but code use should be around the base of the 2 entities.
Details
public class Team
{
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
We have an existing database schema where Employee and EmployeeInfo are separate tables with a FK between EmployeeInfo_Id and Employee_Id.
In our system "managers" will be adding Employee's to the system, with a set of private information (more properties than listed above) like pay, and add them to a Team. Other areas of the system will be using the Team or Employee objects for various other things. We would like to have to code super simple if the mapping can be done.
When a manager creates a new employee we would like the code to look something like this:
public void Foo(string name, decimal pay)
{
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Pay = pay;
// add him/her to the team
_team.Employees.Add(employee); // the idea being that consumers of the Team entity would not get the separate employee info properties
// save the context
_context.SaveChanges();
}
The end result would be that the EmployeeInfo properties entered into the EmployeeInfo table and the base Employee data is entered into the Employee table and added to the Team via the association table TeamEmployees.
So far I'm trying the current mappings, and I get an invalid column named "Discriminator." When just adding an employee to a team.
public class TeamConfiguration : EntityTypeConfiguration<Team>
{
public TeamConfiguration()
{
ToTable("Team");
HasKey(t => t.Id);
HasMany(t => t.Members).WithMany(m => m.Teams)
.Map(m =>
{
m.MapLeftKey("Team_Id");
m.MapRightKey("Employee_Id");
m.ToTable("TeamEmployees");
});
}
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
ToTable("Employee");
ToTable("EmployeeInfo");
HasKey(t => t.Id);
Property(p => p.Name);
HasMany(m => m.Teams)
.WithMany(t => t.Members)
.Map(m =>
{
m.MapLeftKey("Employee_Id");
m.MapRightKey("Team_Id");
m.ToTable("TeamEmployees");
});
}
}
Also, if I take the many-to-many between team and employee out of the mix I get a FK exception on Employee_Id to EmployeeInfo_Id.
Thanks, JR.
Discriminator is a column that's being added to your table when you use Table Per Hierarchy approach.
I think what you're looking for is "Table per Type (TPT)". Decorate your EmployeeInfo class as follows:
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
Or add below to your OnModelCreating event:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<EmployeeInfo>().ToTable("EmployeeInfo");
...
}
Or, create the following class and use it like modelBuilder.Configurations.Add(new EmployeeInfoConfiguration()); in OnModelCreating method:
public class EmployeeInfoConfiguration : EntityTypeConfiguration<EmployeeInfo>
{
public EmployeeInfoConfiguration()
{
ToTable("EmployeeInfo");
}
}
This will cause EF to create EmployeeInfo table with necessary constraints.
Also, it's good to initialize your collections in your objects' constructors to prevent null exception. For example in Team class:
public Team()
{
this.Employees = new HashSet<Employee>();
}
I copied your code exactly, and changed the following parts:
public class Team
{
public Team()
{
this.Members = new HashSet<Employee>();
}
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public Employee()
{
this.Teams = new HashSet<Team>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
In the DbContext, no changes:
public partial class TestEntities : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<EmployeeInfo> Employee_Info { get; set; }
public DbSet<Team> Teams { get; set; }
}
and your working Foo method:
public static void Foo(string name, decimal pay)
{
var _team = new Team();
var context = new TestEntities();
context.Teams.Add(_team);
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Amount = pay;
context.Employees.Add(employee);
context.SaveChanges();
// add him/her to the team
_team.Members.Add(employee);
// save the context
context.SaveChanges();
}
Finally, remove ToTable("EmployeeInfo"); part from EmployeeConfiguration since you have mentioned this correctly in your mode creating event.
For more info about Table Per Type approach, check out this great article.