Problem modelling relationship in Entity Framework using code first - c#

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.

Related

EntityFrameworkCore Populate Property With Polymorphic Association

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"

EF6 - One table to hold all contacts against all other tables

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).

EF 4.1 Code First: Each property name in a type must be unique error on Lookup Table association

This is my first attempting at creating my own EF model, and I'm finding myself stuck attempting to create a lookup table association using Code First so I can access:
myProduct.Category.AltCategoryID
I have setup models and mappings as I understand to be correct, but continue to get
error 0019: Each property name in a type must be unique. Property name 'CategoryID' was already defined
The following models are represented in my code:
[Table("Product", Schema="mySchema")]
public class Product {
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)]
public int ProductID { get; set; }
public int CategoryID { get; set; }
public virtual Category Category { get; set; }
}
[Table("Category", Schema="mySchema")]
public class Category {
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)]
public int CategoryID { get; set; }
public string Name { get; set; }
public int AltCategoryID { get; set; }
}
I have specified the associations with:
modelBuilder.Entity<Product>()
.HasOptional(p => p.Category)
.WithRequired()
.Map(m => m.MapKey("CategoryID"));
I've tried a few other things, including adding the [ForeignKey] annotation, but that results in an error containing a reference to the ProductID field.
You are looking for:
modelBuilder.Entity<Product>()
// Product must have category (CategoryId is not nullable)
.HasRequired(p => p.Category)
// Category can have many products
.WithMany()
// Product exposes FK to category
.HasForeignKey(p => p.CategoryID);

EF 4.1 Bidirectional one-to-one problem

Hi I am having a problem with a simple EF 4.1 code first model.
I have a class person and a class survey that are bidirectionally linked. The database model is correct but I always get this error:
Unable to determine the principal end of an association between the types 'DAL.Models.Survey' and 'DAL.Models.Person'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Class Person
[Key]
public Guid Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public virtual Survey Survey { get; set; }
Class Survey
[Key]
public Guid Id { get; set; }
public bool IsFinished { get; set; }
public virtual Person Person { get; set; }
Datacontext:
modelBuilder.Entity<Survey>().HasRequired(s => s.Person).WithOptional().WillCascadeOnDelete(true);
Can anyone help please
You should define the other navigation property in your mapping since you have it in the model. Otherwise EF will create a second (one-to-many) association:
modelBuilder.Entity<Survey>()
.HasRequired(s => s.Person)
.WithOptional(p => p.Survey)
.WillCascadeOnDelete(true);
I think you have to specify either a foreign key property through HasForeignKey or foreign key column name using Map. Something like this:
modelBuilder.Entity<Survey>()
.HasRequired(s => s.Person)
.WithOptional()
.WillCascadeOnDelete(true)
.Map(m => m.MapKey("fk_column"));

Entity Framework 4.1 Code First Foreign Key Id's

I have two entities referenced one to many. When entity framework created the table it creates two foreign keys, one for the key I have specified with the fluent interface and the other for the ICollection. How do I get rid of the duplicate foreign key?
public class Person
{
public long RecordId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public long DepartmentId { get; set; }
public virtual Department Department { get; set; }
}
public class Department
{
public long RecordId { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
}
Thanks!
You must specify the many-end of the association explicitely:
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany(d => d.People)
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
Otherwise EF will assume that there are two associations: One which is not exposed in Department with the foreign key DepartmentId and navigation property Department in the Person class as you have defined in the Fluent code - and another association which belongs to the exposed navigation property People but with another not exposed end in Person and a foreign key automatically created by EF. That's the other key you see in the database.
The default Code First conventions detect your DepartmentId foreign key, since it is, well, conventional. I think you should remove the Fluent definition:
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.WillCascadeOnDelete(false);
best thing is to remove departmentid property from Person class and add the following statement. MapKey will create foreign key column with the name you specify
modelBuilder.Entity<Person>().HasRequired(p => p.Department)
.WithMany().Map(x=>x.MapKey("DepartmentId"))
.WillCascadeOnDelete(false);

Categories