Okay, I know this one is weird, but I'm trying to have all my contacts in one table; each one will relate to another table using that other table's name and an ID within that table. For example, contact FRED relates to table "Company" with CompanyID 3, whereas contact BARNEY relates to table "Accountant" with AccountantID 21.
public class Contact: DbContext
{
[Key, Column(Order = 0)]
public string TableName { get; set; }
[Key, Column(Order = 1)]
public int ReferenceID { get; set; }
public string ContactName { get; set; }
}
public class Company: DbContext
{
[Key]
public int CompanyID { get; set; }
public string CompanyName { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Accountant: DbContext
{
[Key]
public int AccountantID { get; set; }
public string AccountantName { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
So a foreign key won't work, but the composite key (TableName/ReferenceID) will be unique, so the Company can contain a list of associated Contacts (those having a TableName of "Company" and a ReferenceID that matches the CompanyID). It feels like I'll have to set up the modelBuilder something like this, but I'm really not sure how it would work in this particular case...
modelBuilder.Entity<Company>()
.WithMany(e => e.Contacts)
.HasOptional(d => new { "Company", d.ReferenceID } )
.WillCascadeOnDelete(true);
I'm going to call myself an EntityFrameworkNewbie, so please forgive any obvious oversights. Thanks.
It's not something you'll be able to do in EF in the way you're trying to, there are a couple of ways around it, my suggestion would be many to many relationships (see http://msdn.microsoft.com/en-gb/data/jj591620.aspx):
modelBuilder.Entity<Company>()
.HasMany(t => t.Contacts)
.WithMany(t => t.Companies)
.Map(m =>
{
m.ToTable("CompanyContacts");
m.MapLeftKey("ContactID");
m.MapRightKey("CompanyID");
});
modelBuilder.Entity<Accountant>()
.HasMany(t => t.Contacts)
.WithMany(t => t.Accountants)
.Map(m =>
{
m.ToTable("AccountantContacts");
m.MapLeftKey("ContactID");
m.MapRightKey("AccountantID");
});
etc.
Technically in db terms this means you could have one person be a contact for multiple customers, or a person be a contact for a customer and an accountant at the same time. It's one of these situations where there isn't a correct answer.
Another option would be TablePerType inheritance (so you would create a CompanyContact and an AccountantContact object, both of which inherit from contact (http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt)
Don't do this. This is not a good relational design, and will not give you the referential integrity that is the main benefit of using a relational database in the first place.
If a each related entity can only have one contact, just put the contactId on the related entity. If the related entity can have more than one contact, create a m:n table that properly represents this (e.g. a CompanyContact table).
Related
I am creating a ASP.NET web API project (database first) and it pulls the data from the MSSQL database (read-only access). Database have several tables but there is no primary/secondary keys (we cannot change it). I have set up of one-to-many relations without any problem, but when it comes to the many-to-many, I had to use link tables for holding keys from both side.
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public IList<StudentCourse> StudentCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
public IList<StudentCourse> StudentCourses { get; set; }
}
Link table:
public class StudentCourse
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
Because link table is not present in the database I am getting error of "Data.SqlClient.SqlException: 'Invalid object name 'StudentCourse' ".
public class SchoolContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.\\SQLEXPRESS;Database=EFCore-SchoolDB;Trusted_Connection=True");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.StudentId, sc.CourseId });
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<StudentCourse> StudentCourses { get; set; }
Relationships:
modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.SId, sc.CId });
modelBuilder.Entity<StudentCourse>()
.HasOne<Student>(sc => sc.Student)
.WithMany(s => s.StudentCourses)
.HasForeignKey(sc => sc.SId);
modelBuilder.Entity<StudentCourse>()
.HasOne<Course>(sc => sc.Course)
.WithMany(s => s.StudentCourses)
.HasForeignKey(sc => sc.CId);
I considered Joining tables on these keys but it seems not efficient way of handling relationships and getting related records. What work arounds would you suggest?
Entity Framework is an Object-to-Relational Mapper, Mapper being the key term here. In order to have a relationship between objects to map, there must be a relationship between those entities in the relational database.
Think of it this way, if there is a relationship between students and courses, how is that relationship represented within your database? If I asked you to write two SQL queries against that database, how would you return the following data?
List all students for a specific Course.
List all courses for a specific Student.
You cannot do that with just a Course and a Student table. If there is no linking table then you either have a 1-to-many relationship one way or the other, or the database handles it in a non-relational way. (Such as Student containing a string field with a comma-delimited list of Course IDs) In which case, EF will be of little help.
If the database does not support recording a mapping between students and their courses where you can query how one student can participate in many courses, while each course can have many students participating, then EF cannot be configured to somehow auto-magically read and persist such a relationship. A many-to-many table must exist within the database or such a relationship cannot exist. With EF6 and EF Core 5+ you may read that EF can handle many-to-many relationships without a linking entity, which is true, but that does not mean without a linking table. The table must exist, but you don't need to define a StudentCourse (or CourseStudent) entity.
Instead of:
public IList<StudentCourse> StudentCourses { get; set; }
in both Student and Course, Student can have:
public IList<Course> Courses { get; set; }
while Course has:
public IList<Student> Students { get; set; }
This still maps through a linking table, and provided that relationship just consists of a StudentId and CourseId in the table with no other columns that need to be mapped/configured, EF can manage that relationship and table entirely behind the scenes.
If there is a linking table, just not named what you'd expect: You can call the entity whatever you want and use configuration via attributes, DbContext.OnModelCreating(ModelBuilder), or EntityTypeConfiguration to map that entity and properties to the existing table and columns, however they might be named. But such a table must exist to support that relationship.
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")
);
I have a table Venue which can have multiple Media items on it. Media can be for any number of different tables, so we have 2 properties on it, MediaType to specify which table it's for (content, venue, venueCategory, contact), and MediaTypeID for specifying which item in that table it's for.
How can we populate myVenue.Media when we load our Venues?
We have the following which seems close...
MyDBContext.cs:
builder.Entity<Venue>()
.HasMany<Media>(x => x.Medias)
.WithOne(m => m.Venue)
.HasForeignKey(m => m.MediaTypeID);
Media.cs looks something like:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MediaID { get; set; }
public string URL { get; set; }
public int MediaType { get; set; }
public int MediaTypeID { get; set; }
public Venue Venue { get; set; }
Venue.cs has:
public virtual ICollection<Media> Medias { get; set; }
And then to use the code we're using:
db.Venue.Include(x => x.Medias)
The issue with this is that it doesn't compare on the Media's MediaType property, so we could end up taking in Contact, or Venue Media if they have the same ID.
We could compare in a .Where() after every .Include, but it seems there should surely be a way to do it just the once in the DBContext?
To summarize, my ideal usage would be to specify:
builder.Entity<Venue>()
.HasMany<Media>(x => x.Medias)
.WithOne(m => m.Venue)
.HasForeignKey(m => m.MediaTypeID && m.MediaType==2);
UPDATE:
From feedback in comments it seems this is not possible as EF "only supports associations which can be expressed by db FK relationship. – Ivan Stoev"
I'm trying to learn code first within the Entity Framework and am having trouble modelling a relationship. It's a basic HR database which for the sake of this has two entities, Employees and Departments.
The Employee belongs to a department and the department has a Team Administrator and a Manager, both of whom are in effect employees. I've tried to model this using the following:
EMPLOYEE
public int? DepartmentID { get; set; }
public virtual Department Department { get; set; }
Context:
modelBuilder.Entity<Employee>().HasOptional(x => x.Department);
DEPARTMENT
public class Department
{
[Required]
public int DepartmentID { get; set; }
[Required(ErrorMessage = "The description is required.")]
public string Description { get; set; }
public int? ManagerID { get; set; }
public virtual Employee Manager { get; set; }
public int? TeamAdministratorID { get; set; }
public virtual Employee TeamAdministrator { get; set; }
}
Context:
modelBuilder.Entity<Department>().HasOptional(x => x.Manager);
modelBuilder.Entity<Department>().HasOptional(x => x.TeamAdministrator);
Obviously I would want the Department table to have only four columns - DepartmentID, Description, ManagerID and TeamAdministratorID but it is generating an extra two for the relationship, namely Manager_EmployeeID and Team_Administrator_EmployeeID. Also, in the Employee table the column Department_DepartmentID is generated to store the DepartmentID instead of it using the DepartmentID column I specified in the entity.
What am I doing wrong? How do I need to define the fields and relationships to avoid having code first ignore what I specify and generate it's own navigation fields in the database?
That because your model configuration is incomplete - you started your own mapping with Fluent API so you must tell EF that these properties are indeed FKs for relations. For employee use:
modelBuilder.Entity<Employee>()
.HasOptional(x => x.Department)
.WithMany()
.HasForeignKey(x => x.DepartmentID);
And for department use:
modelBuilder.Entity<Department>()
.HasOptional(x => x.Manager)
.WithMany()
.HasForeignKey(x => x.ManagerID);
modelBuilder.Entity<Department>()
.HasOptional(x => x.TeamAdministrator);
.WithMany()
.HasForeignKey(x => x.TeamAdministratorID);
Btw. without collection navigation properties on opposite side of relations it will be hard to use model (all WithMany are empty). At least Department should have:
public virtual ICollection<Employee> Employees { get; set;}
And mapping should be modified to:
modelBuilder.Entity<Employee>()
.HasOptional(x => x.Department)
.WithMany(y => y.Employees)
.HasForeignKey(x => x.DepartmentID);
See your employee class
EMPLOYEE
public int? DepartmentID { get; set; }
public virtual Department Department { get; set; }
In order to show the relationship between an employee and a department you used ID and Department. In reality you only need to do this once - via Department. EF by default searches for the ID property and links two classes for you. So your classes should only include one ID - ID of the class itself. Try removing IDs to the other classes.
Here is my Model:
public class Customer
{
public int ID { get; set; }
public int MailingAddressID { get; set; }
public virtual Address MailingAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
A customer can have any number of addresses, however only one of those addresses can be a mailing address.
I can get the One to One relationship and the One to Many working just fine if I only use one, but when I try and introduce both I get multiple CustomerID keys (CustomerID1, CustomerID2, CustomerID3) on the Addresses table. I'm really tearing my hair out over this one.
In order to map the One to One relationship I am using the method described here http://weblogs.asp.net/manavi/archive/2011/01/23/associations-in-ef-code-first-ctp5-part-3-one-to-one-foreign-key-associations.aspx
I've struggled with this for almost the entire day and of course I wait to ask here just before finally figuring it out!
In addition to implementing the One to One as demonstrated in that blog, I also then needed to use the fluent api in order to specify the Many to Many since the convention alone wasn't enough with the One to One relationship present.
modelBuilder.Entity<Customer>().HasRequired(x => x.PrimaryMailingAddress)
.WithMany()
.HasForeignKey(x => x.PrimaryMailingAddressID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Address>()
.HasRequired(x => x.Customer)
.WithMany(x => x.Addresses)
.HasForeignKey(x => x.CustomerID);
And here is the final model in the database:
I know you're trying to figure out the Entity Framework way of doing this, but if I were designing this I would recommend not even wiring up MailingAddress to the database. Just make it a calculated property like this:
public MailingAddress {
get {
return Addresses.Where(a => a.IsPrimaryMailing).FirstOrDefault();
}
}