conventions in creating relationships - entity-framework

I'm new to EF, just have some questions on naming conventions in EF, let's say we have two classes Student and Teacher as
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
}
Q1- if Teacher(one) has one to many relationship to Student(many), we know that we need need to add foreign key property and navigation property to Student and modify Teacher class as:
public class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
public IEnumerable<Student> Students { get; set; }
}
so my first question,without attribute or Fluent API, Can I name the navigation property any name other than "Students"? Does the convention works in this way: the type in the IEnumerable is Student, so the property name has to be the type's name plus 's' in the end as Students?
Q2-if Student and Teacher have many-to-many relationship, we need to create a junction class(Let's say it is called Enrolment , and then modify Student and Teacher as:
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public IEnumerable<Enrolment> details { get; set; }
}
public class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
public IEnumerable<Enrolment> details { get; set; }
}
what's the naming convention for both navigation properties in this case? does it mean that we can name the navigation property whatever we want as long as both navigation properties has the same name?

Shortly, the name of the collection navigation property doesn't matter - it could be whatever you like. And since many-to-many via explicit link entity are represented with 2 separate one-to-many relationships, the name of the collection property in both principal entities doesn't matter and also does not need to be one and the same.
The only name which affects the conventional FK property/column name is the name of the principal entity class, the name of the principal entity key property and the name of the reference navigation property in dependent entity if present.
It's partially explained in the Relationships - Conventions section of the EF Core documentation:
If the dependent entity contains a property named <primary key property name>, <navigation property name><primary key property name>, or <principal entity name><primary key property name> then it will be configured as the foreign key.
Note that for one-to-many relationship, the one side entity is always the principal and many side entity is always the dependent.

Related

Multiple One-to-many relationships in the same table. The property is not a valid navigation property on the related type

I am having difficulty mapping this relationship.
public class Person
{
public long Id { get; set; }
[InverseProperty("Friend")]
public virtual ICollection<Friendship> Friends { get; set; }
}
public class Friendship
{
public long Id { get; set; }
public long PersonId { get; set; }
[ForeignKey(nameof(PersonId))]
public virtual Person Person { get; set; }
public long FriendPersonId{ get; set; }
[ForeignKey(nameof(FriendPersonId))]
public virtual Person Friend { get; set; }
}
I get the following error
The InversePropertyAttribute on property 'Friends' on type 'Person' is not valid. The property 'Friends' is not a valid navigation property on the related type 'Friendship'. Ensure that the property exists and is a valid reference or collection navigation property.
I can clearly see that Friendship class does contain a property called Friend, but I am not sure what makes it an invalid navigation property.
If I changed my InverseProperty attribute to FriendPersonId it throws a null reference exception.
The inverse property was set incorrectly.
Should have been [InverseProperty("Person")])
Credit: Ivan Stoev for pointing out my mistake.

EF6 TPT Foreign Key Issue

Say I have this existing schema:
and have this domain mapping as follows:
public class SchoolContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Subject> Subjects { get; set; }
}
protected override OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().ToTable("People");
modelBuilder.Entity<Student>().ToTable("Students");
}
public abstract class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Student : Person
{
public int StudentId { get; set; }
public string Course { get; set; }
public ICollection<Subject> Subjects { get; set; }
}
public class Subject
{
public int SubjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int StudentId { get; set; }
}
And I was given a scenario that I need to query on the subject via PersonId, EF will throw me an exception saying "Invalid column name 'Student_PersonId'".
I understand EF can't see the FK well and I opted to make the Person class as a Base class since there's a chance that I'll have a Teachers table in which it is a Person as well.
Note that Student table need to have its own Primary Key and let's just say the schema was design with a relationship of:
Person -> Student (One-to-Zero or One relationship)
Student -> Subject (One-to-Many relationship)
Is there a way to fix this? Also note that if it's made using Code-First, EF will ommit StudentId on Students table and I do have an existing DB anyway
You should start off by reading this article about TPT in entity framework. Now you don't have a 'Student is a person' kind of relationship, and you'll have to change some things to your database for it to work.
Student's primary key should at the same time be the foreign key to your people table. Since student is a person, it has that database as its baseclass and the Student table should only contain specific properties for student. The properties by Person are inherited.
Person is your abstract base class. Every student, teacher... is a person which is why you can't have a DbSet of Student/teacher... They are persons, so DbSet<Person> is all you need.
You can't map Person to a table. If you really want TPT every person is also a teacher, student... A person alone shouldn't exist, so you shouldn't map it to a table. There's a reason the class is abstract, you can't have just a person. For example Person p = context.Students.FirstOrDefault(); is perfectly valid code for TPT.
That being said, if you think Person can have instances of his own (so certain persons don't have a derived class) you shouldn't opt for TPT and just work with the foreign key to the person table like you do now. If you do want to use TPT you'll have to make above adjustments.

EF: Unable to determine the principal end of an association between the types

Unable to determine the principal end of an association between the types. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Models:
`
[Table("Employees")]
public class Employee : Entity
{
public string Name { get; set; }
public int? AbsenceId { get; set; }
[ForeignKey("AbsenceId")]
public virtual Absence Absence { get; set; }
}
[Table("Absences")]
public class Absence : Entity
{
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Reason { get; set; }
public int? SubstituteId { get; set; }
[ForeignKey("SubstituteId")]
public virtual Employee Substitute { get; set; }
}
`
The Employee have a Absence that can have a Employee that is not same Employee that have a Absence mentioned.
Any solution for this case?
Well, first of all.. You do not need to specify ForeignKey when you are following the Entity Framework conventions. By convention, EF will reocognize the fact that your Navigation property is called Foo and your ForeignKey will be called FooId.
However, the real problem is that you are attempting to create a 1:1 association between two entities and EF does not support associations like this.
EF only supports 1:1 associations with shared primary keys, that is where both tables have the same primary key and one table's PK is a FK to the other table's PK.
If you think about this, it makes sense. There is no native 1:1 relationship in SQL that does not have a shared primary key. If you add a FK in one table to the other, it creates a 1:Many. You can simulate a 1:1 by creating a unique constraint on the FK but EF does not support constraints.
Looking at your model. Do you really want a 1:1 anyways? Can an employee really only have a single absence? Ever? Probably not. You probably want Absence to be a 1:Many. So remove AbsenceId and change Absence to:
public virtual List<Absence> Absences { get; set; }

Navigation properties in separate class

I have a User model with navigation properties for city, town and district tables:
public class User
{
...
public virtual Location_City City { get; set; }
public virtual Location_Town Town { get; set; }
public virtual Location_District District { get; set; }
}
I would like to group these properties in a separate class and re-use that class instead, as shown below:
public class Location
{
public virtual Location_City City { get; set; }
public virtual Location_Town Town { get; set; }
public virtual Location_District District { get; set; }
}
public class User
{
...
public Location Location { get; set; }
public Location ContactPersonLocation {get; set;}
}
I tried various combinations where I removed virtual from Location class and set Location properties of User class as navigation properties instead, but I am getting the following error:
EntityType 'Location' has no key defined. Define the key for this EntityType.
Locations: EntityType: EntitySet 'Locations' is based on type 'Location' that has no keys defined.
Is it possible to have navigation properties in a separate class or do I need to create a separate table?
If you don't want to create separate table for Location, then you should use Complex Type (they don't have key and stored in same table as entity which have complex type property). But here is another problem - as documentation says:
Complex type cannot contain navigation properties.
So, answer is obvious - you should create separate junction table for keeping Location. And Location should be entity with key.

Why use Base class on POCO's with EF Repository Pattern?

I've seen many EF POCO examples where each POCO class inherits a base Entity class or implements an IEntity interface.
I kind of understand why this is used, but I can't see that it will work in all situations, unless I'm missing something.
The Entity base class might look like this:
public class Entity
{
#region Primitive Properties
[Key]
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
[Timestamp]
public byte[] rowversion { get; set; }
#endregion
}
... and the concrete POCO class would look like this:
public class BlogCategory : Entity
{
#region Properties
[Required(ErrorMessage = "Category Name is required.")]
public string CategoryName { get; set; }
public virtual ICollection<Blog> BlogList { get; set; }
#endregion
}
This is fine when all my classes contain a single Primary Key property, but what happens when I have a many-to-many relationship? Usually in a many-to-many relationship, the entity has dual properties that represent the Primary Key of this entity.
Such as:
public class ClaimQuestionAnswer : Entity <-- this will not work, will it?
{
[Key]
public int QuestionId { get; set; }
[Key]
public int AnswerId { get; set; }
public string Answer { get; set; }
public byte[] rowversion { get; set; }
}
Will this particular POCO not inherit the base class?
Any clarification is appreciated.
Thanks.
You might have seen only examples which just don't use any entity classes with composite key. Otherwise they had the same problem you are facing now.
The many-to-many relationship is not the best example because in a true many-to-many relationship the join table does not have a corresponding entity in your model. But you might have for any other reason a composite key in an entity, or you could have entities whose key simply need to have another type (string, long, Guid or whatever).
In this case you cannot use your base class because the key is not a common property anymore for all entities. You could move the key out of the base class and put it into the different derived classes - only DateCreated, DateModified and rowversion are common properties. Or you can create multiple base classes for the different key types you are using.
It all depends what common properties you want to support in all entities.