Mapping optional to many relationship in Entity Framework - c#

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.

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.

How to set up foreign key in Entity Framework with data annotations?

I am trying to build a web application that connects to a SQL Server using Entity Framework. I have three classes, one is to register consultations, the second one is for registering contact information and the last one is to classify consultations types (kind of like an enumerable but I need to store more data).
Every consultation has a contact information (so there is a reference to the primary key of table Contact) and a ConsultationType (another reference to the primary key of table Type).
public class Consultation
{
[Key]
public long id { get; set; }
public long ContactID { get; set; }
[ForeignKey("ContactID")]
public Contact Contact { get; set; }
public long ConsultationTypeID{ get; set; }
[ForeignKey("ConsultationTypeID")]
public ConsultationType Type { get; set; }
}
public class Contact
{
public long id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class ConsultationType
{
public long id { get; set; }
public string type{ get; set; }
public string abbreviated{ get; set; }
}
The Consultation table has 3 columns (id, ContactID and ConsultationTypeID). I am expecting all the Contact and ConsultationType references to be filled automatically based on the ID in the foreign keys.
Currently when I retrieve data from the database, only the ConsultationType gets filled, not the contact. I can not understand this behaviour because I think I have all set up the same way.
EDIT: when I retrieve Consultation data, I get a list of objects with 5 properties (consultation ID, contact ID, contact, consultation type Id, consultation type). 4 of them are set (only contact is not set). I want the contact property to be set to an instance of the row that contains the ID referenced by the Contact ID property.
Rereading the select part.
consultationTypes = await _context.ConsultationTypes.ToListAsync();
Consultation = await _context.Consultations.ToListAsync();
I thought that maybe I should load the Contact also. So I have added:
await _context.ConsultationTypes.ToListAsync();
await _context.Contacts.ToListAsync();
Consultation = await _context.Consultations.ToListAsync();
I don't really understand why I need to do it, but it is working now. I wonder if there is a way to load all tables so I don't have to do it every time.

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

Entity Framework: Using Multiple Junction Tables

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")
);

Entity Framework Mapping join table fields

I have a join table that has a field in it that I need to get into an Entity and have it updatable. I have the Table setup below, the column I need is "PersonelleID" in the "Account" table. Now, there may be multiple, so in that case, there's a concept of primary (think of it as if you were a student who went from one school to another, you'd have the same account but multiple schools).
Any idea how I can bring this into the Entity world? Generating the database ignores this field on the join table (probably because it doesn't know where to put it).
Trying to see what the best route to go is.
if you are using the Code-First approach you would create an entity for the join table that has foreign keys to both Account and School and your extra properties.
public class Account
{
public int AccountId { get; set; }
public string UserName { get; set;}
}
public class AccountSchool
{
[ForeignKey("Account")]
public int AccountId { get; set; }
[ForeignKey("School")]
public string CEEBCodeId { get; set; }
public string PersonelleID { get; set; }
}
public class School
{
[Key]
public string CEEBCodeId { get; set; }
public string Name { get; set;}
}
This is how I've done it and here's is an article explaining how to do it as well:
http://www.itq.nl/blogs/post/Code-First-Entity-Framework-Additional-properties-on-many-to-many-join-tables.aspx

Categories