Having some issues with relationships within EntityFramework 6.
I know that DataAnnotations or FluentApi can be used and I'm okay with using either.
Here's an example of relationship I'd like to accomplish:
Student has one ImmunizationRecord
ImmunizationRecord has Multiple ShotRecords
This seems like it would be fairly straight forward, however it doesn't seem to be working as expected.
Here's example code (Updated from actual code)
public class Student : Entity
{
[Key]
public int Id { get; set; }
public String Name { get; set; }
//...
[ForeignKey(nameof(Id))]
public virtual ImmunizationRecord ImmunizationRecord { get; set; }
}
public class ImmunizationRecord : Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Key]
[ForeignKey(nameof(Student))]
public int StudentId { get; set; }
public String Name { get; set; }
public DateTime LastUpdated { get; set; }
public virtual Student Student { get; set; }
public virtual ICollection<ShotRecord> ShotRecords { get; set; }
}
public class ShotRecord: Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
// Want this to point back to ImmunizationRecord
public int ImmunizationRecordId { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public DateTime DateOfShot { get; set; }
//...
[ForeignKey("ImmunizationRecordId")]
public virtual ImmunizationRecord ImmunizationRecord { get; set; }
}
Example fluentapi might be something like this:
modelBuilder.Entity<Student>().HasOptional(c => c.ImmunizationRecord).WithRequired(m => m.Student);
modelBuilder.Entity<Student>().HasOptional(c => c.ImmunizationRecord).WithRequired(sc => sc.Student);
modelBuilder.Entity<ImmunizationRecord>().HasMany(sc => sc.ShotRecords).WithRequired(sr => sr.ImmunizationRecord);
The Result
I suspect that I'm just missing a small piece of what needs to be done, or missing the proper way to configure these entities with a similar relationship.
With the code above and class structure, I can create a Student, and Create a ImmunizationRecord, and ShotRecords without issue.
The issue occurs when I try to retrieve the ShotRecords from The ImmunizationRecord, EntityFramework will resolve on the key on the Student.Id instead of using the key of on the ImmunizationRecord.Id.
I can go into the database and change the rows for ShotRecords and update the ImmunizationRecordId to the StudentId and they'll resolve properly. But as stated before, I want them to use the key of the ImmunizationRecord, and not the student.
Any advice and guidance would be greatly appreciated!
Thank you in advance!
(Updated to a different example to make more sense)
Related
i am trying to define a database model in code-first to see and display which user is assigned as a specialist for the record data.
I have a very simple model for the user:
public class User
{
public int ID { get; set; }
public string userName { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
....
}
Next I have defined two (simple) models which define the data that can be edited by the user and the specialist should be assigned to using a dropdownlist:
public class Order
{
public int ID { get; set; }
public string orderNumber { get; set; }
public int specialistID { get; set; }
public virtual User specialist{ get; set; }
}
public class Part
{
public int ID { get; set; }
public string partNumber { get; set; }
public string description { get; set; }
public int specialistID { get; set; }
public virtual User specialist{ get; set; }
}
What kind of relation between the models can be used without having a navigation property for each table in the User model?
Do I need to use additional tables to define the relationship: User.Id-Order.specialistID and the relationship: User.Id-Part.specialistID ?
Is there a smarter way out-of-the-box by Entity Framework?
Many thanks for your answers.
Pascal
By default when you add forign-key constraint to the many-to-one table the Entity Framework add virtual property to the entity class and virtual ICollection to the User.
I am using Visual Studio 2012, MVC 4, and Razor (CSHTML). I created Person.Person table in a test database. I wanted to have my model PersonModels.cs use this table so I created the following 2 classes.
public class Person
{
[Key]
public int BusinessEntityID { get; set; }
public string PersonType { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Suffix { get; set; }
public int EmailPromotion { get; set; }
[XmlAttribute(DataType = "string")]
public string AdditionalContactInfo { get; set; }
[XmlAttribute(DataType = "string")]
public string Demographics { get; set; }
public string rowguid { get; set; }
public DateTime ModifiedDate { get; set; }
}
public class PersonDbContext : DbContext
{
public DbSet<Person> person { get; set; }
}
I thought that it would pick the Person.Person table since the class name was Person even though I had not included the schema. However, upon running the application and doing an insert I checked the Person.Person table but the row wasn't there. It created dbo.People table and inserted the row there! I double checked because I thought I might be drunk but I did not write People anywhere!
I read about reverse poco but I'd like to understand how this works more than making it work.
Edit: It worked! Please find the code I used attached:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
namespace client_site.Models
{
[Table("Person", Schema = "Person")]
public class TestModel
{
[Key]
public int ID { get; set; }
}
public class DefaultConnectionX : DbContext
{
public DbSet<TestModel> test { get; set; }
}
}
dbo is the default database schema. If you want to create the table in the people schema you have to add an attribute
[Table("Person", Schema = "Person")]
Ok so I'm adding on to the Simplemembership.
Model UsersProfiles
namespace OilNGasWeb.Models
{
[Table("Users")]
public class UserProfiles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Initials { get; set; }
public string Company { get; set; }
public string Department { get; set; }
public string Title { get; set; }
public string Team { get; set; }
public string TeamSub { get; set; }
public string Phone { get; set; }
public string ImageLocation { get; set; }
public string CurrentlyAuthorized { get; set; }
public string Note { get; set; }
public string Comment { get; set; }
//public virtual dbClient Client { get; set; }
public virtual ICollection<Roles> Roles { get; set; } //many to many
public virtual ICollection<dbClient> Clients { get; set; } // many to many
}
}
Roles
namespace OilNGasWeb.Models
{
[Table("webpages_Roles")]
public class Roles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Required(ErrorMessage = "Required")]
public int RoleID { get; set; }
[Required(ErrorMessage = "Required")]
public string RoleName { get; set; }
public virtual ICollection<UserProfiles> UserProfiles { get; set; } //many to many
}
}
My issue now that i have it creating the many to many tables like i saw it creat before modifications my question is how to get those tables Renamed
webpages_UsersInRoles
I would prefer not to go into SSMS and change them physically rather tell MVC to use a different instance
From the code above EF produced RolesUserProfiles instead of webpages_UsersInRoles
The error shows when the program is trying to #if (User.IsInRole("Administrator")) validade user.
Naturally I hit F12 on IsInRole to bring me to the definition....
it does but there all empty
Now what ? how can i recode if its hidden from me ? where is the code at , and how can i Modify this?
What i would like out of all this is
either renaming the tables ManytoMany as they are being created
being able to modify the code that looks for webpages_UsersInRoles
Thanks in advance.
You cannot rename the tables. The table names are hard coded in SimpleMembership. You can see the source code here:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/WebMatrix.WebData/SimpleMembershipProvider.cs
Don't use the EF navigational properties. You should be accessing this information via the Membership or WebSecurity API's.
If you really want to do this, then you will need to configure EF to use the tablenames required by simple membership, which means utilizing the fluent mapping syntax.. which is not exactly intuitive.
public class Slider_Locale
{
[Key]
public int Slider_LocaleID { get; set; }
[ForeignKey("Culture")]
public int CultureID { get; set; }
public string Slogan { get; set; }
public virtual Culture Culture { get; set; }
}
public class Culture
{
[Key]
public int CultureID { get; set; }
public string CultureName { get; set; }
public string DisplayName { get; set; }
public virtual Slider_Locale slider_Locale { get; set; }
}
It gives error as follows:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role
'Slider_Locale_Culture_Source' in relationship
'Slider_Locale_Culture'. Because the Dependent Role properties are not
the key properties, the upper bound of the multiplicity of the
Dependent Role must be �*�.
How could I design the relationship?. Please help me as I am newbie in mvc and entity.
This is one of those things that's a little tricky to wrap your brain around at first. The issue is that you're trying to set up a 1:1 (or 1:0) mapping, but there's nothing in your model to enforce that kind of mapping. For example, what if you have more than one Slider_Locale object with the same CultureID value? How would your application know which one to pick?
Now, you might know that this will never happen, but the Entity Framework doesn't, and it has to err on the side of caution, so it won't let you set up a relationship that it can't prove is consistent with the table structure. Ideally, it would let you specify unique constraints other than a primary key to work around this, and maybe someday it will, but for now the simplest way around this is to change it to a one-to-many mapping. For example, you could do:
public class Slider_Locale
{
[Key]
public int Slider_LocaleID { get; set; }
[ForeignKey("Culture")]
public int CultureID { get; set; }
public string Slogan { get; set; }
public virtual Culture Culture { get; set; }
}
public class Culture
{
[Key]
public int CultureID { get; set; }
public string CultureName { get; set; }
public string DisplayName { get; set; }
// Note that this got changed to ICollection<>
public virtual ICollection<Slider_Locale> slider_Locales { get; set; }
}
Another thing you could do is change the classes so that they share the same primary key values, but in order to do that you'll have to make at least one of the relationships optional. I could give an example of this if you let me know whether Slider_Locale.Culture can be null, or Culture.slider_Locale, or both.
I'm trying to create a list of train journeys (among other things) in MVC, using code first Entity Framework and wondered how I could map foreign keys for the stations. The Journey model/table will have a DepartureStationID and an ArrivalStationID which will be foreign keys linking to one table/model, called Station.
Here is the code for both these models:
public class Station
{
public int StationID { get; set; }
public string StationName { get; set; }
public string StationLocation { get; set; }
}
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
public virtual Operator Operator { get; set; }
public virtual Station DepartureStation { get; set; }
public virtual Station ArrivalStation { get; set; }
}
There is another foreign key value in there, namely Operator and that has mapped successfully, but the departure and arrivals haven't, and return null values in the view: (#Html.DisplayFor(modelItem => item.DepartureStation.StationName).
When I looked in the database, there had been two additional fields created by EF:
DepartureStation_StationID
ArrivalStation_StationID
And the SQL relationship was between the station table and the two fields above, rather than DepartureID and ArrivalID
So, my question is - Do I need to do something different in the model when referencing the same table for two fields? I don't know why those additional fields were added so I presume I've set up the model incorrectly.
Thanks
For completeness, here's the same thing with fluent configuration.
public class MyDb : DbContext
{
public DbSet<Journey> Journeys { get; set; }
public DbSet<Operator> Operators { get; set; }
public DbSet<Station> Stations { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Journey>()
.HasRequired(j => j.DepartureStation)
.WithMany()
.HasForeignKey(j => j.DepartureID);
builder.Entity<Journey>()
.HasRequired(j => j.ArrivalStation)
.WithMany()
.HasForeignKey(j => j.ArrivalId);
// ... Same thing for operator ...
base.OnModelCreating(builder);
}
}
Edit: To address your above comment about the cascade delete, you can add .WillCascadeOnDelete(false) after .HasForeignKey() and that might help (although you'll then have to delete Journey records manually)
Add the folowing attributes on your navigation properties :
public class Journey
{
public int JourneyID { get; set; }
public int DepartureID { get; set; }
public int ArrivalID { get; set; }
public int OperatorID { get; set; }
public string JourneyCode { get; set; }
[ForeignKey("OperatorID")]
public virtual Operator Operator { get; set; }
[ForeignKey("DepartureID")]
public virtual Station DepartureStation { get; set; }
[ForeignKey("ArrivalID")]
public virtual Station ArrivalStation { get; set; }
}
And of course you need to regenerate your database in order to apply the new configuration.
Hope this will help.