Can't get EF code first one-to-many working - c#

Picking up from post:
Define one-to-one in EF code first with fluent API
where I had trouble getting a one-to-one working, I now have another problem with a one-to-many.
Here are my two classes:
[Table("PSCStatuses")]
public class PSCStatus
{
[Key]
public int PSCStatusID { get; set; }
public string StatusCode { get; set; }
public string StatusTextDesc { get; set; }
public int NumDaysToProjEndDate { get; set; }
public virtual List<Case> Cases { get; set; }
}
public class Case
{
// Key: Tells EF this is the PK for Case.
// ForeignKey("Appointee"): tells EF Appointee is the Principle/Parent in the 1:1
[Required]
[Key, ForeignKey("Appointee")]
public string ProfileID { get; set; }
[Required]
public int? PSCStatusID { get; set; }
public virtual PSCStatus PSCStatus { get; set; }
public virtual Appointee Appointee { get; set; }
}
You can see here what I had to do in my previous post to get Case to have one Appointee. (And Appointee to have a Case where Appointee is the Principle/Parent).
I don't recall ever having to jump through hoops with EF before. But I think I am very rusty here.
Now after solving that I have a new problem.
I can't get Case to fill in PSCStatus.
When I inspect Case.PSCStatus at a break point after adding the case with the PSCstatusID set, I should see the PSCStatus object filled in and populated.
But it remains null.
I would think that the definition above would tell EF everything it needs to know but it is not working.
I also tried fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Case>()
.HasRequired<PSCStatus>(c => c.PSCStatus)
.WithMany(s => s.Cases)
.HasForeignKey(c => c.PSCStatusID);
}

Use:
ICollection insead of List
[Edit]
If this doesn't work try this model builder:
modelBuilder.Entity<Case>()
.HasRequired(c => c.PSCStatus)
.WithMany(s => s.Cases)
.HasForeignKey(c => c.PSCStatusID);

Related

How to properly enable EF cascade delete with conventions

I'm using EF 6.4.0 codefirst on a winforms app and Cascade delete is not working
below are my CLASSES
public class PLAYERS_M
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PM_ROWID { get; set; }
public string PM_PLAYER_ID { get; set; }
public string PM_FULLNAME { get; set; }
public int? PM_COUNTRY { get; set; }
public bool PM_IS_HOH { get; set; }
public string PM_QUOTE { get; set; }
public byte[] PM_PHOTO { get; set; }
[ForeignKey("PM_COUNTRY")]
public virtual COUNTRIES COUNTRIES { get; set; }
}
public class COUNTRIES
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CO_ROWID { get; set; }
public string CO_CODE { get; set; }
public string CO_NAME { get; set; }
}
I've added the following method to enable cascade delete on the dbcontext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Add<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Add<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
however I get the following error
"The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.PLAYERS_M_dbo.COUNTRIES_PM_COUNTRY". The conflict occurred in database "MpContext", table "dbo.PLAYERS_M", column 'PM_COUNTRY'."
if you have observed the foreign key PM_COUNTRY is nullable int.
so I was expecting EF to delete the countries record and set PM_COUNTRY to null
am I doing something wrong?
I'm not sure why the convention isn't working, but you can configure it like this (after renaming the Entities):
modelBuilder.Entity<Player>()
.HasOptional(c => c.Country)
.WithMany()
.HasForeignKey(p => p.CountryID)
.WillCascadeOnDelete(true);
OP wants to DELETE a COUNTRIES record and expects that all PLAYERS_M that have the corresponding PM_COUNTRY value will be set to null.
To achieve this, PLAYERS_M.PM_COUNTRY is correctly defined as nullable field (int?) but you still need to declare the relationship as optional in the model to enforce this behaviour.
OneToManyCascadeDeleteConvention does not help here, according to the documentation:
Convention to enable cascade delete for any required relationships.
To get around this you can add the following Fluent Notation to your OnModelCreating method:
modelBuilder.Entity<PLAYERS_M>()
.HasOptional(p => p.COUNTRIES)
.WithMany()
.HasForeignKey(p => p.PM_COUNTRY)
.WillCascadeOnDelete(true); // this is where the magic is!
This behaviour is most likely by design, using Cascade Delete like this to null out optional references can very quickly create orphaned records, by using fluent notation you are forced to make the business decision for each relationship individually. Use this feature with care.
I've solved my problem by adding the below property to the COUNTRIES class
[ForeignKey("PM_COUNTRY")]
public virtual List<PLAYERS_M> PLAYERS_M { get; set; }
public class COUNTRIES
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CO_ROWID { get; set; }
public string CO_CODE { get; set; }
public string CO_NAME { get; set; }
[ForeignKey("PM_COUNTRY")]
public virtual List<PLAYERS_M> PLAYERS_M { get; set; }
}

Foreign Key binding to object

I have some model created, which is supposed to store GUIDs (strings) as identifiers and I want the class that has a foreign key with it to also have the object to be accessed. I remembered while using LinqToSql was pretty simple, but now I'm running into some troubles with EF.
I have tried putting the object as virtual but I think I have to do something on the DbContext, because when I try to create the migration it fails due to recursion limit (I guess this is due to making sort of circular references).
For example, I have the following classes:
public class Calculation
{
[Required]
public Guid Id { get; set; }
[Required]
public Guid UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
[Required]
public string Title { get; set; }
[Required]
public Guid CausativeId { get; set; }
[ForeignKey("CausativeId")]
public virtual Causative Causative { get; set; }
[Required]
public bool Active { get; set; }
}
public class Causative
{
[Required]
public Guid Id { get; set; }
[Required]
public string Document { get; set; }
public virtual Calculation Calculation { get; set; }
}
The idea is that the Calculation knows who is it's Causative, but I also want to be able to do it the other way around (this was something pretty useful on LinqToSql), if this is not possible, just automatically filling the Causative property with the corresponding data without the need of explicitly making another call to the database.
Should I do something in the DbContext instance? I have this in place:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Calculation>().HasOne(calculation => calculation.Causative).WithOne(causative => causative.Calculation);
}
I'm sure I'm doing something wrong, but I'm not sure what.
Thanks in advice!
For that model you created, you should change this one for OnModelCreating:
modelBuilder.Entity<Causative>()
.HasOne(p => p.Calculation)
.WithOne(i => i.Causative)
.HasForeignKey<Calculation>(b => b.CausativeId);
and this is for one to many if you have IEnumerable<Calculation> in Causative:
modelBuilder.Entity<Calculation>()
.HasOne(p => p.Causative)
.WithMany(b => b.Calculation);
I think that you just need to replace this line:
public virtual Calculation Calculation { get; set; }
with this:
public virtual List<Calculation> Calculations { get; set; }
or this:
public virtual ICollection<Calculation> Calculations { get; set; }
this will create for you the right navigation property and FK

Add a one-to-many connection to Many-to-Many

I have a Many To Many relationship with some additional fields. But as there are Photos added to the many to many relationship which might apply to other relations I wanted to seperate it so I can change it by just altering the One to many relation. This is the model
public class Segment
{
public int SegmentId { get; set; }
public int ConnectionPointIdEnd { get; set; }
public string ConnectionName { get; set; }
public string ConnectionInformation { get; set; }
public string Image { get; set; }
public string Direction { get; set; }
public ICollection<ConnectionPointRoute> ConnectionPointRoutes { get; set; }
}
public class ConnectionPointRoute
{
public int ConnectionPointId { get; set; }
public int RouteId { get; set; }
public int SegmentId { get; set; }
public int Position { get; set; }
public ConnectionPoint ConnectionPoint { get; set; }
public Route Route { get; set; }
public Segment Segment { get; set; }
}
And the modelbuilder looks like this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ConnectionPointRoute>()
.HasKey(c => new { c.ConnectionPointId, c.RouteId, c.SegmentId });
modelBuilder.Entity<ConnectionPoint>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.ConnectionPoint)
.HasForeignKey(c => c.ConnectionPointId);
modelBuilder.Entity<Route>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Route)
.HasForeignKey(c => c.RouteId);
modelBuilder.Entity<Segment>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Segment)
.HasForeignKey(c => c.SegmentId);
}
And this all works well for getting the items, but for some reason it doesn't allow me to post a new Route for instance, it gets me the error:
"Multiplicity constraint violated. The role
'Segment_ConnectionPointRoutes_Source' of the relationship
'InBuildingNavigator.Data.Models.Segment_ConnectionPointRoutes' has
multiplicity 1 or 0..1."
Any thoughts?
Fixed this! I had an error in my Post code, I added the full child objects which doesn't make a whole lot of sense in my case.
Ask me if you want a more detailed fix!
Just two more things to this:
I would recommend you to use an extra object for the many-to-many relationship (if you don't already do this). This will give you more control over the table name and over selections you may want to do.
use the virtual keyword for your properties, which you do not need directly (for your collections) - this will allow ef to implement lazy loading on them.

How do I create a Many-to-Many relationship with only one entity?

Courses have many prerequisites, and simultaneously a particular course can be a prerequisite for many courses. I've tried to establish the many-to-many relationship (in OnModelBCreating) using EF code-first with the following:
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany(e => e.Postrequisites)
.Map(m => m.ToTable("CourseRequisiteMappings")
.MapLeftKey("CourseId").MapRightKey("CourseId")); // EDIT: THIS LINE IS THE PROBLEM. SEE MARKED ANSWER AND MY COMMENT ON IT.
Also, here is the Course class:
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<Course> Prerequisites { get; set; }
public virtual ICollection<Course> Postrequisites { get; set; }
}
When I implemented this and went to update the database, it gave me the following errors:
CourseId: Name: Each property name in a type must be unique. Property
name 'CourseId' is already defined.
ModuleId: Name: Each property name in a type must be unique. Property
name 'ModuleId' is already defined.
CourseCourse: EntityType: EntitySet 'CourseCourse' is based on type
'CourseCourse' that has no keys defined.
ModuleModule: EntityType: EntitySet 'ModuleModule' is based on type
'ModuleModule' that has no keys defined.
I could not find an example of doing this which leads me to believe one of the following three are true:
There's a different way of accomplishing this that I don't see
I'm on the right track but overlooking something due to my lack of knowledge with EF
I'm the first one to try and EF doesn't support this (very unlikely)
First, does anyone know how I can set up this relationship, i.e., what do these errors mean (responding to #2)? For bonus points, is there another way of doing this that might be better or worse (kinda #1)? Thanks in advance.
Your mapping is nearly correct. But you have to understand that under the hood the Entity Framework wil create a so callled junction table that stores the many to many relationship.
This junction table wil just have two fields, containing the foreign keys which together make up the primary key. Obviously these foreign keys cannot have the same name.EF is smart enough to all figure it out by itself and no maping is necessary. Below a working example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace ManyToManyUnderTheHoodSpike
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<CourseContext>());
using (CourseContext context=new CourseContext())
{
context.Courses.Add(new Course("Top of the bill")
{
PrerequisiteCourses = new List<Course>()
{
new Course("My two cents"),
new Course("Counting to two")
}
});
context.SaveChanges();
}
}
}
public class CourseContext : DbContext
{
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class Course
{
public Course() { }
public Course(string name)
{
Name = name;
}
public string Name {get;set;}
public int CourseId{get;set;}
public ICollection<Course> PrerequisiteCourses{get;set;}
public ICollection<Course> FollowUpCourses{get;set;}
}
}
If you run this code you get a database with two tables: Courses and CourseCourses with as the only fields Course_Id and Course_Id1.
But that is not very readable, so let's make the mapping to make it more readable:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>().HasMany(course => course.PrerequisiteCourses)
.WithMany(course => course.FollowUpCourses)
.Map(data => data.ToTable("Prerequisites")
.MapLeftKey("FollowUpId")
.MapRightKey("PrerequisiteId"));
}
Presto!
I would model like this. I know you wanted only 1 table. But Ef will create the many to many table if you dont. Not sure what you didnt get right without testing. So anyway, here is another option.
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
//nav elements
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<PreReqCourse> Prerequisites { get; set; }
// You can Find follow on courses, by accessing PreReqCourse table, but if you felt this navigation offered enough value, create a post req table too. Using same approach.
// public virtual ICollection<Course> Postrequisites { get; set; }
}
public class PreReqCourse
{
public virtual int Id {get; set;}
public virtual int CourseId { get; set; }
public virtual Course PreReqForCourse { get; set; } //Nav prop
}
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany();
// Leave WithMany empty. You can define in PreReqCourse Table model, you dont need to model from both directions.
modelBuilder.Entity<PreReqCourse>()
.HasRequired(e => e.PreReqForCourse)
.HasForeignKey(f => f.CourseId)
.WithMany(p=>p.PreRequisites);

Relationship troubles with Entity Framework

I need help creating the relationship in entity framework as everything I have tried gives me errors when trying to add the migration or if I get passed that then I try to update the database and get an error about indexes with the same name.
public class Profile
{
public Profile()
{
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string VersionCreated { get; set; }
public string DiskLocation { get; set; }
public string Name { get; set; }
public DateTime DateTime { get; set; }
public virtual Product Product { get; set; }
public virtual Instance OriginalInstance { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
public class Instance
{
public Instance()
{
TestResults = new HashSet<TestResult>();
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string UserFriendlyName { get; set; }
public virtual Product Product { get; set; }
public virtual Profile LastKnownProfile { get; set; }
public virtual Computer Computer { get; set; }
public virtual ICollection<TestResult> TestResults { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
The problem with the above classes is that the OrginalInstance property on the Profile class and the LastKnownProfile in the Instance class are supposed to just be foreign keys to those specific tables and they probably won't be the same very often. They can also both possibly be null.
I have tried:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile);
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance);
This gave me an Unable to determine the principal end of an association between the types 'EcuWeb.Data.Entities.Instance' and 'EcuWeb.Data.Entities.Profile'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. error.
and with:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile).WithOptional();
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance).WithOptional();
The database adds a foreign key reference back to itself.
...that the OrginalInstance property on the Profile class and the
LastKnownProfile in the Instance class are supposed to just be foreign
keys to those specific tables and they probably won't be the same very
often. They can also both possibly be null.
In this case you actually want two one-to-many relationships between Profile and Instance if I don't misunderstand your quote above. It would mean that many Profiles can have the same OriginalInstance and that many Instances can have the same LastKnownProfile. The correct mapping would look like this then:
modelBuilder.Entity<Profile>()
.HasOptional(p => p.OriginalInstance)
.WithMany()
.Map(m => m.MapKey("OriginalInstanceId"));
modelBuilder.Entity<Instance>()
.HasOptional(i => i.LastKnownProfile)
.WithMany()
.Map(m => m.MapKey("LastKnownProfileId"));
The lines with MapKey are optional. Without them EF will create a foreign key with a default name.
Also note that you must use HasOptional (instead of HasRequired) if "both can possibly be null".

Categories