Im building a management portal for a chain of restaurants. I am using ASP.NET MVC with EF Code First.
I want each user to, after login, only see the rescources that are connected to them. I want to put a junction table(many-to-many) between ApplicationUser and the Restaurant-class(model), since each user can have/work at many restaurants, and each restaurant can have many owners/workers.
How do you do this in EF Code first? The same way I did Restaurant --> Menue? Do you need to build a new DBContext for Applicationuser for this to work?
public class Restaurant
{
public int Id { get; set; }
public string Name { get; set; }
public string Adress { get; set; }
public string PhoneNumber { get; set; }
public DateTime StartDate { get; set; }
//Connections
public virtual ICollection<Menue> Menues { get; set; }
}
public class Menue
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public DateTime ModifyDate { get; set; }
//FK For RestaurantConnection
public int RestaurantId { get; set; }
}
For many to many configuration do like this
Student class should have a collection navigation property for Course, and Course should have a collection navigation property for student
public class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
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; }
}
In your DbContext add this configuration
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
}
For more information read this article Configure Many-to-Many relationship
Related
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int MainCourseId { get; set; }
public Course MainCourse { get; set; }
public IList<StudentCourse> StudentCourses { get; set; }
}
public class Course
{
public int Id { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
}
public class StudentCourse
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
There are two issues.
First issue is, the above migration is not creating the MainCourseId , gives this error.
"Introducing FOREIGN KEY constraint 'FK_Students_Courses_MainCourseId' on table 'Students' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors."
Second is, in another system where structure of entities are same, updating an existing record with a modifed value in MainCourseId doesn't work.
Your db structure has a bug. You need to add List of StudentCourse to Course class too. IMHO it is not the best db structure I have seen. Much better way is to add Id to StudentCourse table and a flag if it is a main course.
public class StudentCourse
{
public int Id { get; set; }
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
public bool IsMainCourse {get; set;}
}
and since you are using net5 you can add another lists to the classes
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Course> Courses { get; set; }
public virtual List<StudentCourse> StudentCourses { get; set; }
}
public class Course
{
public int Id { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
public virtual List<Student> Students{ get; set; }
public virtual List<StudentCourse> StudentCourses { get; set; }
}
you need to make a new init migration to use this structure
You can't have cascade delete that creates a multiple cascade paths. Here deleting a Course would delete both all the StudentCourses for that Course and all the Students that have that as their MainCourse.
But EF won't configure CascadeDelete if your MainCourse is optional, as it probably should be to enable you to store a Student that has no MainCourse. So to fix both issues, just make Student.MainCourseId optional by changing the type to int?.:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int? MainCourseId { get; set; }
public Course MainCourse { get; set; }
public IList<StudentCourse> StudentCourses { get; set; }
}
Or you can leave it as required and configure the relationship not to cascade deletes.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasOne(s => s.MainCourse)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<StudentCourse>()
.HasKey(e => new { e.StudentId, e.CourseId });
base.OnModelCreating(modelBuilder);
}
I think it might be more convenient to use "skip navigation properties" so Student has Courses, and a Course has Students, which you can do in EF Core 5+ with Many-to-Many mapping, like this:
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System;
using System.Collections.Generic;
namespace EfCore6Test
{
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int? MainCourseId { get; set; }
public Course MainCourse { get; set; }
public ICollection<Course> Courses { get; } = new HashSet<Course>();
}
public class Course
{
public int Id { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
public ICollection<Student> Students { get; } = new HashSet<Student>();
}
public class StudentCourse
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
public class Db: DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses{ get; set; }
public DbSet<StudentCourse> StudentCourses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasOne(s => s.MainCourse).WithMany().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Student>().HasMany(s => s.Courses).WithMany(c => c.Students).UsingEntity<StudentCourse>(
sc => sc.HasOne(e => e.Course).WithMany(),
sc => sc.HasOne(e => e.Student).WithMany()
);
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;database=efCore6Test;Integrated Security=true;TrustServerCertificate=true", o => o.UseRelationalNulls(true))
.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
{
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var joe = new Student() {Name="Joe" };
var math = new Course() { CourseName = "Math" };
var english = new Course() { CourseName = "English" };
joe.Courses.Add(math);
joe.Courses.Add(english);
joe.MainCourse = english;
db.Students.Add(joe);
db.SaveChanges();
}
{
using var db = new Db();
var joe = db.Students.Where(s => s.Name == "Joe").First();
var math = db.Courses.Where(c => c.CourseName == "Math").First();
joe.MainCourse = math;
db.SaveChanges();
}
}
}
}
How do I define the relationships here with EF Core?
I have an Employee table which has multiple Jobs
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<HourlyRate> DefaultRate { get; set; }
public string Note { get; set; }
public bool Active { get; set; }
public DateTime DateHired { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
public List<Address> Addresses { get; set; }
public List<Job> Jobs { get; set; }
public bool Deleted { get; set; }
}
And the Job class has an Employee object to navigate back to the employee and the Job has multiple Directors which are also Employees
public class Job
{
public int JobId { get; set; }
public Employee Employee { get; set; }
public JobType Type { get; set; }
public Department Department { get; set; }
public List<Employee> Directors { get; set; }
public bool Active { get; set; }
public decimal HourlyRate { get; set; }
public string Note { get; set; }
public bool Deduction { get; set; }
public int? DeductionPercent { get; set; }
}
This is my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasMany(employee => employee.Jobs)
.WithOne(i => i.Employee);
}
Initially the Job only had a single Director and everything was good but the requirement has changed to have multiple directors and ef removed the Director column from the Job table and added a JobId column to the Employee table but the problem is that if i add that director to a second job by job.Directors.Add(director) EF overrides the job id of the of the director and the director is being removed from the previous job
I am using EF Core 2.2
if a Job has only 1 Employee but multiple Directors (also Employee)
add public int EmployeeId {get; set;} to your Job class and add this
modelBuilder
.Entity<Job>()
.HasMany(p => p.Directors)
.WithMany(p => p.Jobs));
also, change List<> to ICollection<>
You should tell EF through fluent API that there's a 1-to-many relationship from Employee to Job. Otherwise, EF may get confused.
The many-to-many relationship needs a junction table and matching entity in the model which you'll also need to configure through fluent API. You'll define two 1-to-many relationships from Employee and Job to that new entity. EF core does not directly support such relationships before 5.0.
If you are targeting SQL, then you need to mark at least one of the relationships as OnDelete(CascadeBehavior.NoAction). Otherwise, your model will generate invalid table defintions which will raise errors at creation time.
Update:
The junction table would be defined something like this.
public class Employee
{
// ... other stuff
public List<EmployeeJob> EmployeeJobs { get; set; }
}
public class Job
{
// ... other stuff
public List<EmployeeJob> EmployeeJobs { get; set; }
}
public class EmployeeJob
{
public int EmployeeId { get; set; }
public int JobId { get; set; }
public Employee Employee { get; set; }
public Job Job { get; set; }
}
// goes in DbContext
modelBuilder.Entity<EmployeeJob>.HasKey(x => new { x.EmployeeId, x.JobId });
Try to use this code. Since your employee can have one or many jobs I added the table EmployeeJob and many-to-many relations. I think you just need to add IsDirector flag to Employee or maybe better something like an EmployeeType:
public class Employee
{
public Employee()
{
EmployeeJobs = new HashSet<EmployeeJob>();
}
[Key]
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Note { get; set; }
public bool Active { get; set; }
public DateTime DateHired { get; set; }
public bool Deleted { get; set; }
[InverseProperty(nameof(EmployeeJob.Employee))]
public virtual ICollection<EmployeeJob> EmployeeJobs { get; set; }
}
public class Job
{
public Job()
{
EmployeeJobs = new HashSet<EmployeeJob>();
}
[Required]
public int JobId { get; set; }
public bool Active { get; set; }
public decimal HourlyRate { get; set; }
public string Note { get; set; }
public bool Deduction { get; set; }
public int? DeductionPercent { get; set; }
[InverseProperty(nameof(EmployeeJob.Job))]
public virtual ICollection<EmployeeJob> EmployeeJobs { get; set; }
}
public class EmployeeJob
{
[Key]
public int Id { get; set; }
public int EmployeeId { get; set; }
[ForeignKey(nameof(EmployeeId))]
[InverseProperty(nameof(EmployeeJob.Employee.EmployeeJobs))]
public virtual Employee Employee { get; set; }
public int JobId { get; set; }
[ForeignKey(nameof(JobId))]
[InverseProperty(nameof(EmployeeJob.Employee.EmployeeJobs))]
public virtual Job Job { get; set; }
}
public class EmployeeDbContext : DbContext
{
public EmployeeDbContext()
{
}
public EmployeeDbContext(DbContextOptions<EmployeeDbContext> options)
: base(options)
{
}
public DbSet<Employee> Employees { get; set; }
public DbSet<Job> Jobs { get; set; }
public DbSet<EmployeeJob> EmployeeJobs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=Employee;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EmployeeJob>(entity =>
{
entity.HasOne(d => d.Employee)
.WithMany(p => p.EmployeeJobs)
.HasForeignKey(d => d.EmployeeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_EmployeeJob_Employee");
entity.HasOne(d => d.Job)
.WithMany(p => p.EmployeeJobs)
.HasForeignKey(d => d.JobId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_EmployeeJob_Job");
});
}
I just created a model with some database structures I'd like to created
namespace Ability.Models
{
public class Skill
{
[key]
public int ID { get; set; }
public string SkillName { get; set; }
public virtual List<Teacher> Teachers { get; set; }
}
public class Teacher
{
[key]
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Campus { get; set; }
public virtual List<Skill> Skills { get; set; }
}
public partial class AbilityDbContext : DbContext
{
public AbilityDbContext()
: base("name= DefaultConnection")
{
}
public virtual DbSet<Teacher> Teachers { get; set; }
public virtual DbSet<Skill> Skills { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Teacher>()
.HasMany(s => s.Skills)
.WithMany(c => c.Teachers);
base.OnModelCreating(modelBuilder);
}
}
}
However I have no idea how I can generate the migrations for these new tables. I thought it would give me a message when I try to Update-Database, so that I could use Add-Migration, but it just says
No pending explicit migrations.
So my question is, how can I let the Entity framework do the work for me and create the correct migrations?
Run the below commands in package manager console from visual studio under Tools menu item in order to have migration work
enable-migrations
add-migration AddTeacher
add-migration AddSkill
update-database
Also, if those entities are added new then you need to decorate them with Table attribute like
[System.ComponentModel.DataAnnotations.Schema.Table("Skills")]
public class Skill
{
[key]
public int ID { get; set; }
public string SkillName { get; set; }
public virtual List<Teacher> Teachers { get; set; }
}
[System.ComponentModel.DataAnnotations.Schema.Table("Teachers")]
public class Teacher
{
[key]
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Campus { get; set; }
public virtual List<Skill> Skills { get; set; }
}
I have a basic model like that :
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
public Contact PrincipalContact { get; set; }
public int? PrincipalContactId { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public Account Account { get; set; }
public int? AccountId { get; set; }
}
Entity Framework creates two columns on the table Contacts: Account_Id and AccountId.
The column AccountId is always null. I don't know why I have this behavior
What is the proper way to achieve this with Entity Framework ? I tried to add the [ForeignKey] attribute but it doesn't change anything.
Thank you.
In the case of one-to-one relationships, you will need to provide some additional
information so that Code First knows which entity is the principal and which is
the dependent.
public class Account
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
[ForeignKey("PrincipalContact")]
public int? PrincipalContactId { get; set; }
public virtual Contact PrincipalContact { get; set; }
}
public class Contact
{
[Key]
[ForeignKey("AccountOf")]
public int Id { get; set; }
public string Name { get; set; }
public virtual Account AccountOf { get; set; }
}
The Account_Id column is created automatically by EF based in the one-to-many relationship. If you don't specify nothing, by convention, EF will recognize the fact that your Navigation property is called Account and your ForeignKey will be called AccountId, but due to you have a property with the same name, EF changes it to Account_Id.
To create both relationships that you need I recommend you modify the model as I show below:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
public Contact PrincipalContact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public Account Account { get; set; }
public int? AccountId { get; set; }
}
Then, In your Context you can configure the relationships explicitly using Fluent Api.
public class YourContext : DbContext
{
public IDbSet<Account> Accounts { get; set; }
public IDbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>()
.HasOptional(c => c.Account)
.WithMany(e => e.Contacts)
.HasForeignKey(a => a.AccountId);
modelBuilder.Entity<Account>()
.HasOptional(c => c.PrincipalContact)
.WithMany()
.Map(c => c.MapKey("PrincipalContactId"));
}
}
Update
If you want to keep the PrincipalContactId property on the Account class, you should map the relationship this way:
modelBuilder.Entity<Account>()
.HasOptional(c => c.PrincipalContact)
.WithMany().HasForeignKey(a => a.PrincipalContactId);
These are my entities:
public class Department
{
public Department()
{
Employees = new List<Employee>();
}
public int ID { get; set; }
public string Name { get; set; }
public IList<Employee> Employees { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
}
public class User
{
public int ID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int EmployeeID { get; set; }
public Employee Employee { get; set; }
}
Department has many employees. and every user has just one employee (one to one). how can i achive this relation with fluent code first?
thanx.
Since you are not using a shared primary key, you can map it as a "One-to-Many" relationship and ignore the "Many" side.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<User>()
.HasRequired(u => u.Employee)
.WithMany()
.HasForeignKey(u => u.EmployeeId);
}