Entity Framework: Using Multiple Junction Tables - c#

I've designed a database, paying a lot of attention to normalization.
Here's a piece of it:
First of all, If you notice any issues with this design, feel free to let me know.
The goal is to have companies, each of which have some departments.
Departments can be shared between companies.
As in:
Company 1 can have Department 1, 2 and 3.
Company 2 can have Department 1, 5, 8 and 9.
The BusinessUnits will have access to departments.
But it depends on the company to which a department is linked.
BusinessUnit 1 may have permission to access Department 1 of Company 1, but should not be able to access Department 1 of Company 2.
The CompanyDepartment config table is pretty obvious.
It links a Company to (possibly) multiple departments.
The CompanyDepartmentBusinessUnit config table is used to link BusinessUnits to Departments of a Company.
In this table, the CompanyId and DepartmentId form a composite Foreign Key to the primary key of CompanyDepartment (which is: CompanyId and DepartmentId as well).
I'm using a Database-First approach in Entity Framework.
For the simple junction tables, I've overwritten the OnModelCreating method in my DbContext.
An example of how I did this:
My question now is: how do I do this for the CompanyDepartmentBusinessUnit relation?
Say that my user chose to see the departments of Company 1.
I want to filter all the Departments that are linked to Company 1 but are also visible to the BusinessUnit in which the user resides (for instance Business Unit 2).
Thank you in advance and enjoy your holidays!

EF allows you to use implicit junction table only if (1) it has no additional columns and (2) if it's not referenced by other entity different than the two ends of the many-to-many relationships.
CompanyDepartment satisfies the condition (1), but not (2) because it's referenced from CompanyDepartmentBusinessUnit, hence you need to use explcit entity with two one-to-many relationships.
Once you do that, it can be seen that now CompanyDepartmentBusinessUnit satisfies both conditions, hence can be modelled with implicit junction table for BusinessUnit and CompanyDepartment.
With that being said, the final model would be something like this:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyDepartment> DepartmentLinks { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyDepartment> CompanyLinks { get; set; }
}
public class BusinessUnit
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsPersonal { get; set; }
public ICollection<CompanyDepartment> CompanyDepartments { get; set; }
}
public class CompanyDepartment
{
public int CompanyId { get; set; }
public int DepartmentId { get; set; }
public Company Company { get; set; }
public Department Department { get; set; }
public ICollection<BusinessUnit> BusinessUnits { get; set; }
}
and taking into account the default EF conventions, with the following minimal fluent configuration:
modelBuilder.Entity<Company>().ToTable("Company");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<BusinessUnit>().ToTable("BusinessUnit");
modelBuilder.Entity<CompanyDepartment>().ToTable("CompanyDepartment");
modelBuilder.Entity<CompanyDepartment>()
.HasKey(e => new { e.CompanyId, e.DepartmentId });
modelBuilder.Entity<CompanyDepartment>()
.HasMany(e => e.BusinessUnits)
.WithMany(e => e.CompanyDepartments)
.Map(m => m
.MapLeftKey("CompanyId", "DepartmentId")
.MapRightKey("BusinessUnitId")
.ToTable("CompanyDepartmentBusinessUnit")
);

Related

Microsoft.EntityFrameworkCore.DbUpdateException: 'An error occurred while saving the entity changes. SqlException: Invalid column name 'UsersUserID'

I'm having issues when I try and add a record to my DB. The view is registration and the following error occurs on _db.SaveChanges() when trying to add to Secretaries/Student.
SqlException: Invalid column name 'UsersUserID'.
Screenshot of error(1). Screenshot of Error(2)
I manually added changes to the Migration/Migration Snapshot and removed the column UsersUserID as it duplicated FK UserID. I've tried searching for the column but there are no occurrences. Deleting migrations and creating a new DB does not solve the problem either. Does anyone have any idea on how to solve this problem?
Here is my Users Model.
public class Users
{
[Key]
public int UserID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public int SuburbID { get; set; }
public string Title { get; set; }
public int RoleID { get; set; }
public string ErrorMessage { get; set; }
public virtual ICollection<Secretaries> Secretaries { get; set; }
public virtual ICollection<Student> Student { get; set; }
public virtual Suburb Suburb { get; set; }
public virtual Role Role { get; set; }
}
Here is my Student Model
public class Student
{
[Key]
public int StudentID { get; set; }
public int UserID { get; set; }
[Required]
public DateTime DateJoined { get; set; } = DateTime.Now;
[StringLength(1)]
[Required]
public char Is_Active { get; set; } = 'T';
public virtual Users Users { get; set; }
}
Here is my secretaries model
public class Secretaries
{
[Key]
public int SecretaryID { get; set; }
public int UserID { get; set; }
[Required]
public DateTime DateJoined { get; set; } = DateTime.Now;
[StringLength(1)]
[Required]
public char Is_Active { get; set; } = 'T';
public virtual Users Users { get; set; }
}
This is the section where I get the error
public IActionResult Registration(Users users)
{
try
{
//users.Role.RoleID;
_db.Users.Add(users);
//_db.SaveChanges();
int role = users.RoleID;
int id = users.UserID;
if (role == 1)
{
Student student= new Student();
student.UserID = id;
_db.Student.Add(student);
_db.SaveChanges();
}
else if (role == 2)
{
Secretaries secretary = new Secretaries();
secretary.UserID = id;
_db.Secretaries.Add(secretary);
_db.SaveChanges();
}
return RedirectToAction("Index", "Home");
}
catch
{
return View(users);
}
}
To start, naming convention would help around losing the Plural for the entity. "Users" implies multiple, and your naming is inconsistent given "Student". EF can manage details like having a table name "Users" with an entity named "User". Worst case you can use the [Table("Users")] attribute if there are naming issues. The entity can use a meaningful name even if it doesn't match what the table is called.
The issue you are likely running into is that while EF can auto-resolve some relationships between entities and things like FKs by convention, unless you follow the known conventions closely, EF can miss some details requiring you to provide them. Personally I opt to keep configuration pretty deliberate and consistent, not to rely on convention and the occasional surprise when it doesn't work. In your case two likely issues is marrying the User to their declare FK, and then also the bi-directional mapping.
With the entity and property named "Users", EF likely cannot match this to the UserId by convention, at best it would probably be looking for "UsersId", so when it didn't find that it would look for TableNamePKName so "UsersUserID" where "Users" is the table name and "UserID" is the PK in that table. To resolve that:
[ForeignKey("UserID")]
public virtual Users Users { get; set; }
This tells EF where to look. If this is a later version of C# then:
[ForeignKey(nameof(UserID))]
... to avoid magic strings if available.
The next detail that may trip up the mapping will be the bi-directional references where a Secretary has a User and User has a collection of secretaries. Bi-directional references should be avoided unless they serve a very clear and useful purpose. In most cases they are not needed, but where you do you them it helps to declare the inverse side of the relationship:
[ForeignKey("UserID")]
[InverseProperty("Secretaries")]
public virtual Users Users { get; set; }
This tells EF to expect a navigation property called "Secretaries" on the Users entity which will link via this User reference.
Bi-directional references can cause issues and require more attention when updating entities, especially if you have code that does something like serialize an entity. They often aren't needed as you can typically query data from one side of the relationship.
For instance to get all Secretaries associated to a given user you might think you need something like:
var user = context.Users.Include(u => u.Secretaries).Single(u => u.UserID == userId);
var secretaries = user.Secretaries;
... when instead you can use:
var secretaries = context.Secretaries.Where(s => s.User.UserID == userId).ToList(); // or (s => s.UserID == userId) where the UserID FK is exposed.
Depending on the purpose of the association you schema may better be served by inheritance where instead of a "has a" relationship, the Secretary/Student to User becomes an "is a" relationship. Does a User "have" Secretaries/Students, or is it more that a User "Is a" Secretary vs. a Student? Alternatively, is "Student" vs. "Secretary" more of a "Role" that can be held by a User? (Which suits a case where one user might be both a Student and a Secretary)
With the schema you currently have defined, it would allow one user to be potentially associated with many Students, as well as many Secretaries which may, or may not be the desired and intended use. (Also known as Many-to-One relationships)
Edit: To implement a schema where a User can be one of these Types or Roles there are a couple of options.
Inheritance: Where a User "is a" Teacher or "is a" Student etc. EF can support inheritence where you would have something like:
public abstract class User
{
public int UserId { get; set; }
// other common fields...
}
public class Student : User
{
// Student-specific fields...
}
public class Secretary : User
{
// Secretary-specific fields...
}
From here you can configure EF to either use a single table (Users) with a discriminator column (Think "UserType" or "Role" to indicate Student vs. Secretary, etc.) or to use a User Table plus Student and Secretary tables. You can learn more about inheritance options by looking up "Table-per-Hierarchy" or "Table-per-Concrete Type" in relation to EF. If the different types of users have different fields then Table-per-concrete type is generally a better option. Table-Per-Hierarchy can work but you end up with several null-able-columns as the table needs to cater to the lowest denominator.
If the different types of users consist of the exact same data then you could use something called a Many-to-one relationship between a User table, and a UserType or a UserRole table which would identify whether the user was a Student, Teacher, etc. This can be either Many-to-One, (many users can be the same role, but each user can have only 1 role) or it can be adapted to many-to-many if users could later hold 2 or more roles.
Many-to-one:
public class User
{
public int UserId { get; set; }
public int RoleId { get; set; }
[ForeignKey(nameof(RoleId))]
public virtual Role Role { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
}
Many-to-Many
public class User
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual ICollection<Role> Roles { get; set; } = new List<Role>();
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
}
This would need to be configured with a .HasMany(x => x.Roles).WithMany() relationship nominating a joining table (I.e. UserRoles) consisting of a UserId and RoleId. Optionally in a Many-to-Many relationship you can define the joining table as an entity if there are details about the relationship you want to access. (beyond just the keys relating the entities)
The other advantage of using a Many-to-One or Many-to-Many vs. inheritance is that it's easy to express optional roles, where a user might not be any of the roles. You can still adapt something like a Many-to-One implementation to have Student-specific data vs. Teacher-specific data by introducing a One-to-zero-or-one relationship between User and a table like StudentUserDetails on the UserId shared by both tables.
Ultimately there are a number of ways you can manage relational relationships for data in a database where EF can be configured to understand and map those relationships either within, or between entities.

One-to-one relationship in Entity Framework with ASP.NET Identity tables [duplicate]

I am having an issue getting a reference to the employee object from the PayGroup object using Entity Framework 6.1. I have a foreign key in the database on PayGroup.SupervisorId -> Employee.EmployeeId. Note that this is a zero or one-to-one relationship (a pay group can only have one supervisor and an employee can only be the supervisor of one pay group).
According to this post on GitHub, it is not possible to have a foreign key on a table with a different primary key. I've added the foreign key to the database manually but I can't figure out how to set up the fluent api mapping to be able to get the employee object from pay group.
Pay Group Table
Employee Table
Note: There is a foreign key from PayGroup.SupervisorId - Employee.EmployeeId in the database.
Below are the DTO's (I don't currently have any working relationship mapping between these classes):
public class PayGroup
{
public int Id { get; set; }
public string SupervisorId { get; set; }
public virtual Employee Supervisor { get; set; }
}
public class Employee
{
public string EmployeeId { get; set; }
public string FullName { get; set; }
}
one-to-one relationship with explicit FK property (like your PayGroup.SupervisorId) is not supported.
So remove that property from the model:
public class PayGroup
{
public int Id { get; set; }
public virtual Employee Supervisor { get; set; }
}
and use the following fluent mapping:
modelBuilder.Entity<PayGroup>()
.HasRequired(e => e.Supervisor)
.WithOptional()
.Map(m => m.MapKey("SupervisorId"));
The WithOptional() call specifies two things. First that there is no inverse navigation property in Employee class, and second that the FK is optional (Allow Nulls = true in the table).
If you decide to add inverse navigation property
public class Employee
{
public string EmployeeId { get; set; }
public string FullName { get; set; }
public virtual PayGroup PayGroup { get; set; } // <=
}
change it to WithOptional(e => e.PayGroup).
If you want to make it required (Allow Nulls = false in the table), then use the corresponding WithRequiredDependent overload (Dependent here means that the Employee will be the principal and PayGroup will be the dependent).

Entity framework code first. How to add a list of an object type file to an object that already has a property of that type

Have an object Journal.
Journal has a Company field.
[Required]
[Display(Name = "Company")]
public int CompanyID { get; set; }
[ForeignKey("CompanyID")]
public Company Company { get; set; }
Company has a list of Journals
public IList<Journal> Journals { get; set; }
Now couple of months after the project went live, there is a request for a Journal to have a possibility to have multiple Companies assigned to it.
Simple solution would be to just change the object field into:
public IList<Company> Companies { get; set; }
mapped in the many to many table between Journals and Companies but as stated at the start, project and the database is live in production. The only solution I see is adding list of companies (multiCompanies) field on top of the Company field.
No idea how to to it in Entity though.
You could try and approach this problem with a mapping table to normalize your new requirement which is causing the relationship between two tables to become M:M. Below is the example of how it could look like in EF.
public class Company
{
[Key]
public int Id { get; set; }
public virtual ICollection<CompanyJournals> CompanyJournals { get; set; }
}
public class Journal
{
[Key]
public int Id { get; set; }
public virtual ICollection<CompanyJournals> CompanyJournals { get; set; }
}
public class CompanyJournals
{
[Key]
public int Id { get; set; }
public int CompanyId { get; set; }
public int JournalId { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
[ForeignKey("JournalId")]
public virtual Journal Journal { get; set; }
}
So original situation:
- Company-Journal is one-to-many relation.
- Every Company has zero or more Journals,
- every Journal belongs to exactly one Company
Desired situation:
- Company- Journal is a many-to-many relation
- Every Company has zero or more Journals
- Every Journal has zero or more Companies.
Many to many relations are usually done using a junction table. In entity framework classes you won't need this junction table. In your queries you'll use the ICollections:
class Company
{
public int Id {get; set;} // primary key
// every Company has zero or more Journals
public virtual ICollection<Journal> Journals {get; set;}
... // other properties
}
class Journal
{
public int Id {get; set;} // primary key
// every Journal is used by zero or more Companies
public virtual ICollection<Company> Companies{get; set;}
... // other properties
}
If you want to change the configuration of an existing entity framework configured database you'll need to implement a migration. See Entity Framework Code First Migrations
What you'll have to do in the migration:
(1) Create a junction table Company_Journals
Two fields:
(a) int CompanyId, required, foreign key to Company
(b) int JournalId, required, foreign key to Journal
The combination of these two fields will be the primary key
See CreateTable
class AddCompanyJournalsTable: DbMigration
{
public override void Up()
{
CreateTable("dbo.Company_Journals",
c => new
{
CompanyId = c.Int(nullable: false, identity: true)
JournalId = c.Int(nullable: false, identity: true)
});
The return value of a CreateTable is a TableBuilder object which can be used to define the primary and foreign keys
.PrimaryKey(t => new {t.CompanyId, t.JournalId})
.ForeignKey("dbo.Companies", t => t.CompanyId, cascadeDelete: false)
.ForeignKey("dbo.Journals", t => t.JournalId, cascadeDelete: false)
(2) For every Journal you had in the previous version create one Company_Journal
pointing to the company and the journal:
IQueryable<Journal> allJournals = ...
foreach (Journal journal in allJournals)
{
dbContext.Journals.Add(new Journal()
{
JournalId = journal.Id, // or whatever primary key you have in Journal
CompanyId = journal.CompanyId
});
}
(3) Remove the Journal.CompanyId using DropColumn

Mapping optional to many relationship in Entity Framework

Some background: I inherited the database. Other applications are running against it, so I can't change the structure. We have a table of employees and a table of activities. Some of the employees are supervisors and the activities can optionally have a supervisor specified to indicate that only that particular supervisor can have their employees work that particular activity.
The way the database is set up, an employee is a supervisor if the SupID field is not null and that is the field that the SupID column in Activity refers to.
Here's a sample of the classes I'm working with:
public class Emp
{
public int EmpID { get; set; }
public string Fname { get; set; }
public string Lname { get; set; }
public int SupID { get; set; }
public virtual ICollection<Activity> SupervisedActivities { get; set; }
}
public class Activity
{
[Key]
public string ActNum { get; set; }
public int SupID { get; set; }
public virtual Emp Supervisor { get; set; }
}
The problem that I'm having is that no matter how I map the relationships between these two classes, EF wants to relate the SupID on Activity to the EmpID on Emp instead of the SupID. Is it possible to do what I'm trying to do in EF without modifying the database?
No you cannot do that.
Up to EF6 it's not possible to make a 1-to-many relation using an AK (alternate key) on the 1 side. The 1 side must always be the PK in the parent table.
MS has a site where you can vote for new EF features, and this is usually one of the top requested features: Unique Constraint (i.e. Candidate Key) Support
You can vote for it here, if you want.

How to configure many to many relationship using entity framework fluent API

I'm trying to set up a many to many relationship in EF code first but the default conventions is getting it wrong. The following classes describes the relationship:
class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
class Account
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
One Account can have many Products.
However the EF conventions will create the DB tables as:
Products Table
--------------
Id
Name
Account_Id <- What is this?
Accounts Table
--------------
Id
Name
This doesn't look like a many-to-many table structure? How can i get configure the fluent API to reflect the relationship and create an intermediary table:
AccountProducts Table
---------------------
Account_Id
Product_Id
modelBuilder.Entity<Account>()
.HasMany(a => a.Products)
.WithMany()
.Map(x =>
{
x.MapLeftKey("Account_Id");
x.MapRightKey("Product_Id");
x.ToTable("AccountProducts");
});
What EF has suggested is a one-to-many relationship.
One Account can have many Products, i.e. each Product has an Account_Id
If you want a many to many relationship (and create an intermediary table), the following should work
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
class Account
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Code first is creating tables in right relational way. When
One Account can have many Products.
Products table should store key for its Account, as it actually does now.
What you are trying to see in db, is many-to-many relationship, not one to many. If you want to achieve that with code first, you should redesign your entities
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
class Account
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
In this case, one product can have many accounts, and one account can have many products.
public AccountProductsMap()
{
this.ToTable("AccountProducts");
this.HasKey(cr => cr.Id);
this.HasMany(cr => cr.Account)
.WithMany(c => c.Products)
.Map(m => m.ToTable("AccountProducts_Mapping"));
}

Categories