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.
Related
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.
This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.
Three code-first objects, related to one another.
public class Schedule
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public decimal Rate { get; set; }
public Type Type { get; set; }
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
}
When I query this, I want all related types, so:
Schedule currentSchedule = _Context.Schedules
.Include("Charges.Type")
.Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
.First();
In C#, this has been working fine.
The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.
So I change it to this:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:
Unable to determine the principal end of an association between the
types 'Core.Charge' and 'Core.Type'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations
Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:
public class Type
{
[Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.
What's going on, and what have I done wrong?
You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.
Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.
If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public int ChargeId { get; set; }
[ForeignKey("ChargeId")]
public virtual Charge Charge { get; set; }
}
I am trying to code the following in code first... since I am just begining I am not able to.. please help.. thanks in advance
1. Student: Student will have student ID, First Name, Last Name
Student should belong to one class and one section(basically one to one relationship with each entity)
2. Classes: Class will have ClassId, Name
Class should have collection of students and collection of sections(basically many to many relationship with each entity)
3. Sections: Section will have SectionID, Name
Section should belong to one class and should have collection of students(basically one to one relation with class and one to many relation with Students)
Below is the code for the same
Students.cs
public class Students
{
public int StudentsId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public decimal Grade { get; set; }
public int ClassesId { get; set; }
public Classes Classes { get; set; }
public int SectionsId { get; set; }
public Sections Sections { get; set; }
}
Classes.cs
public class Classes
{
public int ClassesId { get; set; }
public string Name { get; set; }
public ICollection<Sections> Sections { get; set; }
}
Sections.cs
public class Sections
{
public int SectionsId { get; set; }
public string Name { get; set; }
public int ClassesId { get; set; }
public Classes Classes { get; set; }
public ICollection<Students> Students { get; set; }
}
If I do this I get error saying:
Introducing FOREIGN KEY constraint
'FK_dbo.Sections_dbo.Classes_ClassesId' on table 'Sections' may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I know I can get rid of this error using fluent APIs and telling not to cascade on delete, but I don't want to do that. Is there any other solution to this?? Please help
With your current model, no, there is no other way than disabling casdading delete for some of the relationships.
All your relationships are required, that means that if a class is deleted you delete the sections and the students of that class (Classes has a not exposed collection of students due to the required navigation property Classes in Students). But if the sections are deleted the students of that sections are deleted as well - and that's the second delete path to Students.
I don't know the exact meaning of your model but to me it sounds strange to delete all students of a class if the class gets deleted. Does a student always must have a class or couldn't he temporarily be without class assignment (and section assignment as well)? Maybe the student has a holiday semester for half a year and doesn't participate in any class?
In that case you could make the relationships of Students optional. Just declare the foreign key properties as nullable:
public class Students
{
//...
public int? ClassesId { get; set; }
public Classes Classes { get; set; }
public int? SectionsId { get; set; }
public Sections Sections { get; set; }
}
This would fix your problem of multiple cascading delete paths in the Students class because by default optional relationships don't have cascading delete enabled. The relationship between Classes and Sections is still required, so deleting a class will delete all sections belonging to the class as well, but it won't delete the students anymore.
We have the following set of objects.
public class Form
{
public int FormId { get; set; }
public DateTime DateCreatedOn { get; set; }
public string Status { get; set; }
}
// This is using TPT inheritance from Form
[Table("FormA")]
public class FormA : Form
{
public string ExtraInfoA { get; set; }
public virtual ICollection<Child> Children
}
// This is using TPT inheritance from Form
[Table("FormB")]
public class FormB : Form
{
public string ExtraInfoB { get; set; }
public virtual ICollection<Adult> Adults
}
public class Person
{
public int PersonId { get; set; }
public int FormId
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
// This is using TPH inheritance from Person
public class Adult : Person
{
public int HowManyCars { get; set; }
public string NationalInsuranceNo { get; set; }
[ForeignKey("FormId")]
public virtual FormB FormB { get; set; }
}
// This is using TPH inheritance from Person
public class Child : Person
{
public int HowManyToys { get; set; }
public string SchoolName { get; set; }
[ForeignKey("FormId")]
public virtual FormA FormA { get; set; }
}
This creates 3 tables for the forms Form, FormA, and FormB, all with the appropriate fields in them. It also creates 1 table for Person.
The problem is When we rely on the convention and don't specify the ForeignKey attribute the Person table contains 2 additional foreign key columns.
However when we do specify the ForeignKey attribute (as in the code above) we get the following error message.
`The foreign key component 'FormId' is not a declared property on type 'Child'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.`
FormId is definitely a property of Child so I'm not sure what is going wrong.
Our real world situation is a lot more complicated that the situation above so I'd like to get it right now rather tham have multiple foreign keys.
Any help is very much appreciated.
You cannot define foreign key in the parent entity and navigation property in the child entity. They must both be defined in the same entity. What you are trying to do is even not valid in the database because you cannot have conditional foreign key constraint on the column - constraints to both FormA and FormB will be applied for every record and you will never be able to insert any record (because it would always violate constraint to FormA or FormB).
In short: You need either single navigation property in parent or separate foreign key for every child.
My Model:
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class Location
{
public string Address { get; set; }
public virtual int CountryId { get; set; }
public virtual Country Country { get; set; }
}
public class User{
protected User()
{
Location = new Location();
}
public int UserId { get; set; }
public Location Location { get; set; }
}
When generating the database, I get:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmEntityType: : EntityType 'Location' has no key defined. Define the key for this EntityType.
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �Locations� is based on type �Location� that has no keys defined.
How do I have a navigational property inside a complex type? If I remove the country navigational property, it works fine.
Navigation properties (refering to other entities) on a complex type are not supported. You must either make your Location an entity (with its own table) or remove the navigation property Country from Location (and add the [ComplexType] attribute as mentioned by Steve Morgan).
Edit
Reference: http://msdn.microsoft.com/en-us/library/bb738472.aspx
"Complex type cannot contain navigation properties."
EF wants to infer a primary key for Location, but can't.
Add a public int LocationId { get; set; } to the Location class and it should be happy.
If you want to use Location as a complex type, annotate it with a [ComplexType] attribute.