EF 4.1 Cascading in two directions between entities with two relationships - entity-framework

I have the following two entities:
public class Tournament {
public int TournamentID { get; set; }
public String Name { get; set; }
public int? OfficialID { get; set; }
public virtual Official HeadOfficial { get; set; }
public virtual ICollection<Official> Officials { get; set; }
}
public class Official {
public int OfficialID { get; set; }
public String Surname { get; set; }
public String FirstName { get; set; }
public int TournamentID { get; set; }
public virtual Tournament Tournament { get; set; }
}
A tournament can have 0..N officials and an official HAS to have a tournament linked to it. The second relationship is that a tournament may have a headofficial.
To make the EF properly interpret this when making the database I have the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Tournament>()
.HasOptional(t => t.HeadOfficial)
.WithMany()
.HasForeignKey(t => t.OfficialID);
modelBuilder.Entity<Tournament>()
.HasMany(t => t.Officials)
.WithRequired(o => o.Tournament)
.HasForeignKey(o => o.TournamentID);
}
If I now delete a tournament, all the officials that were linked to that tournament are deleted, which is what I want. However, when I delete an official that has been made head official in one of the tournaments, the delete doesn't go through citing that the delete statement conflicted with the reference constraint in Tournaments.OfficialID.

If I now delete a tournament, all the officials that were linked to
that tournament are deleted, which is what I want. However, when I
delete an official that has been made head official in one of the
tournaments, the delete doesn't go through citing that the delete
statement conflicted with the reference constraint in
Tournaments.OfficialID.
I think these are quite different situations. The first case works because there is a cascading delete setup in the database (EF did create this rule in the database because the relationship is required, that's default behaviour).
Your second relationship is optional, so there is no cascading delete by default. More important: I don't think that you want cascading delete because it would mean that deleting an official would also delete the tournaments which have this official as head.
I can only imagine that you want to set the HeadOfficial reference to null if the official gets deleted. But there is no automatic way (like for cascading delete). You must program such a behaviour, for example:
using (var ctx = new MyContext())
{
var officialToDelete = ctx.Officials.Single(o => o.OfficialID == 5);
var tournaments = ctx.Tournaments.Where(t => t.OfficialID == 5).ToList();
foreach (var tournament in tournaments)
tournament.OfficialID = null;
ctx.Officials.Remove(officialToDelete);
ctx.SaveChanges();
}
I believe that the foreach loop is not necessary because EF will fixup the relationships (= set the foreign key to null) when you call Remove. (Basically EF itself does this loop internally.) But it is important that the tournaments are loaded into the context because EF cares about this relationship fixup (only for attached (=loaded) entities), not the database (in contrast to the cascading delete).

Related

EF Core Deleting related entities on One To One relationship

I have a one to one relationship set up on ef core. When i try to delete Article
entity I need to cascade MediPlan since it is one to one relationship. When I Delete Article, MediaPlan is not getting removed.
Here is set up.
public class Article
{
public int Id { get; set; }
public int MediaPlanId { get; set; }
public MediaPlan MediaPlan { get; set; }
}
and
public class MediaPlan
{
public int Id { get; set; }
public Article Article { get; set; }
}
Context
modelBuilder.Entity<Article>().HasOne(x => x.MediaPlan).WithOne(x => x.Article);
Code to delete
var article = await _db.Articles
.Include(x=>x.MediaPlan)
.SingleAsync(x=>x.Id == id);
_db.Articles.Remove(article);
await _db.SaveChangesAsync();
Do I have to set a FK on MediaPlan entity as well?
Thank you!
I see your One-to-One Fluent API configuration is not written in correct way as you did not specify the dependent entity. Your Fluent API configuration should be written as follows:
modelBuilder.Entity<Article>().HasOne(a => a.MediaPlan)
.WithOne(mp => mp.Article)
.HasForeignKey<Article>(a => a.MediaPlanId)
.OnDelete(DeleteBehavior.Cascade);
Now deleting a MediaPlan will also delete its dependent Article as follows:
var mediaPlanToBeDeleted = await _db.MediaPlans.FirstOrDefaultAsync(x=>x.Id == id);
_db.MediaPlans.Remove(mediaPlanToBeDeleted);
await _db.SaveChangesAsync();
Now if you want the reverse behavior then you have to reverse your Fluent API configuration.
Note: Only deleting the principal entity will cascade delete the dependent entity. Vice-versa is not possible.

Entity Framework Cascading Delete

First of all, apologies if I'm missing some basic stuff here but I'm new to EF and still getting my head around setting up the DB code first....
I'm having a similar problem to this Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths but can't seem to work out from the comments there what I need to do with my particular model. When I attempt to update database after adding in public virtual Actor actor { get; set; } to my UseCase class, I get this error:
Introducing FOREIGN KEY constraint 'FK_dbo.UseCase_dbo.Actor_ActorID' on table 'UseCase' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
I know it must be something to do with the way that my FK constraints are set up (probably something to do with deleting a use case meaning that I'll end up deleting data from multiple other tables).
I tried turning off cascading delete, but still get the error:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//prevent table names created by entity framework being pluralised
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
//Turn off delete cascades between parent child tables. May need to put this back in future, but for the time being it is stopping the database being updated through the package manager console (error is that a foregin key constraint may cause cycles or multiple cascade paths)
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
Here are my models. What should happen is that only if a project is deleted should it's use cases or actors be deleted. Actors should not be deleted when a UseCase is, because they may be involved in other UseCases. Can anyone point to what I need to change?
Finally, the correct model indeed is this Project > Actors > Use Cases. I assume that I should just remove public virtual int ProjectID { get; set; } and public virtual Project project { get; set; } from UseCase?
Learning hurts!
public class Project
{
public virtual int ID {get; set;}
[DisplayName ("Project Name")]
public virtual string ProjectName { get; set; }
[DisplayName("Client")]
public virtual string ClientID { get; set; }
public virtual string Description { get; set; }
[DisplayName("Use Cases")]
public virtual ICollection <UseCase> UseCases { get; set; }
}
public class UseCase
{
public virtual int ID { get; set; }
[Required]
public virtual int ProjectID { get; set; }
public virtual int ActorID { get; set; }
[Required]
public virtual Actor actor { get; set; }
public virtual string Title { get; set; }
public virtual Level? Level { get; set; }
public virtual string Precondition { get; set; }
public virtual string MinimalGuarantee { get; set; }
public virtual string SuccessGuarantee { get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Extension> Extensions { get; set; }
public virtual ICollection<Query> Queries { get; set; }
}
public class Actor
{
public virtual int ID { get; set; }
public virtual int projectID { get; set; }
public virtual Project project { get; set; }
public virtual string Title { get; set; }
[DataType(DataType.MultilineText)]
public virtual string Description { get; set; }
}
UPDATED So, here is my modified code based on feedback below. I'm still getting the same error, either when I run the application and it tries to create the DB or when I try to update the database through package manager Update-Database. Driving me crazy.
To me, the code below says if I delete an actor, delete the use cases for that actor too. If I delete a project, delete the actors for the project and therefore delete the use cases for each actor too. But if I delete a project, don't delete the use cases. Clearly, I'm misunderstanding something quite badly :-(
modelBuilder.Entity<Actor>()
.HasMany(a => a.useCases)
.WithRequired(uc => uc.actor)
.HasForeignKey(uc => uc.ActorID)
.WillCascadeOnDelete(true); // and this works
modelBuilder.Entity<Project>()
.HasMany(p => p.actors)
.WithRequired(a => a.project)
.HasForeignKey(a => a.projectID)
.WillCascadeOnDelete(true); // this works
modelBuilder.Entity<Project>()
.HasMany(p => p.UseCases)
.WithRequired(uc => uc.project)
.HasForeignKey(uc => uc.ProjectID)
.WillCascadeOnDelete(false); // disable this cascading delete
You need to disable cascade deletes for all but one of the possible paths. In your case you have the following paths:
Project -> UseCase
Project -> Actor -> UseCase
You can allow a single path for cascading deletion of UseCase - via the Project entity or Actor entity. However, if we disable cascading deletes in the Project -> UseCase path, we'll still achieve a cascading delete via Actor:
modelBuilder.Entity<Project>()
.HasMany( p => p.UseCases )
.WithRequired( uc => uc.Project )
.HasForeignKey( uc => uc.ProjectID )
.WillCascadeOnDelete( false ); // disable this cascading delete
modelBuilder.Entity<Project>()
.HasMany( p => p.Actors )
.WithRequired( a => a.Project )
.HasForeignKey( a => a.ProjectID )
.WillCascadeOnDelete( true ); // this works
modelBuilder.Entity<Actor>()
.HasMany( a => a.UseCases )
.WithRequired( uc => uc.Actor )
.HasForeignKey( uc => uc.ActorID )
.WillCascadeOnDelete( true ); // and this works
Side note:
Your model has a data inconsistency hazard - both Actor and UseCase have a FK to Project via ProjectID, but there is nothing in the model to enforce the Actor referenced by a UseCase has the same ProjectID - an Actor from "Project 1" could be reference by a UseCase from "Project 2". You could include the ProjectID in the Actor PK and then in the UseCase->Actor FK, ensuring that the Actor referenced by a UseCase belongs to the same Project, but this would technically violate the 2NF.
The 'proper' model is probably a Project->Actors->UseCases hierarchy, simply requiring you to join through Actors to get a Project's UseCases
You need to make ActorID in your UseCase class as a nullable int. EF is throwing that error because it sees 2 foreign keys that are required in a single class. Having that would create multiple cascade paths--something that SQL Server is, unfortunately, ill-equipped to handle.
In any case, making Actor optional on your UseCase class will mean that the Actor won't be deleted when the UseCase is, which I believe is your intent.

Entity Framework Generated Column Names

I have a Job Entity which has 2 collections of the same type
public virtual ICollection<Device> ExistingDevices { get; set; }
public virtual ICollection<Device> NewDevices { get; set; }
On the Device Entity, it refers back to the Job
public int JobId { get; set; }
public virtual Job Job { get; set; }
On the surface, this works just fine, however on the database, if you look at a device you see this
//Devices Table in Db
|JobId | Job_Id | Job_Id1 |
My setup includes Entity configuration objects for using fluent API, however I have not worked out how to fix this. The first JobId is fine, it is a perfect description of the data. The second two refer to the ExistingDevices and NewDevices lists they belong to. The headers are not at all descriptive.
Is it possible to rename these columns to something more appropriate?
//Edit
It has the FK JobId but also if the device is in the ExistingDevices list, the JobId also gets put into Job_Id and Job_Id1 is null. If the device belongs to the NewDevices list, the Job_Id is null and the Job_Id1 has the JobId in it.
Naming the Job_id to ExistingDevices and Job_Id1 to NewDevices would make it much clearer in Db.
Update
Having slept on it, I decided it was a design fault.
I changed the Device Model to have
public bool NewDevice { get; set; }
and changed the Job Model by removing the 2 existing ICollections and adding
public virtual ICollection<Device> Devices { get; set; }
Rather than having two device collections, I now have one, with a properly descriptive FK. In the database a 1 or 0 will indicate new or existing device.
You have two one to many relationship, the database will have two foreign key columns on the dependent entity (Device). JobId might represent ExistingDevices and JobId1 might represent NewDevices.
To be clear you should define two navigation properties as follow.
public int? ExistingJobId { get; set; }
public virtual Job ExistingJob { get; set; }
public int? NewJobId { get; set; }
public virtual Job NewJob { get; set; }
Then can configure the relationship using Fluent Api.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Device>()
.HasOptional(x => x.ExistingJob)
.WithMany(x => x.ExistingDevices)
.HasForeignKey(x => x.ExistingJobId);
.WillCascadeOnDelete(true);
modelBuilder.Entity<Device>()
.HasOptional(x => x.NewJob)
.WithMany(x => x.NewDevices)
.HasForeignKey(x => x.NewJobId)
.WillCascadeOnDelete(false);
}
Note, one foreign key is defined without cascading delete (false) because multiple cascading delete is not allowed.
update: the required existing job and new job have been changed into optional.

Will one-to-one foreign key associations be supported in EF v-Next?

I have entities A and B and I want to create 2 distinct 1-1 associations between A and B. A should play the role as principal. Like this:
public class A
{
public int Id {get; set;}
public B B1 {get; set;}
public B B2 {get; set;}
}
public class B
{
public int Id {get; set;}
}
Since EF does not support one-to-one foreign key associations I cannot create a working model/database with EF. To my this sounds like a serious limitation. Are there any plans to support such associations in an upcoming version of EF?
What is the best workaround for to get this working. I know about creating two one-2-many associations. However, that would make B the principal and gives me problems with cascading deletes.
Thanks for replying to my question. Below is an example of what I want to do, i.e., create two (or more) 1-to-1 associations between an entity A and another entity B. Is this something that EF could support in vNext, or else, why would it be a bad idea?
Thanks again,
Merijn
public class A
{
public int Id {get; set;}
public int B1_Id {get; set;}
public B B1 {get; set;}
public int B2_Id {get; set;}
public B B2 {get; set;}
}
public class B
{
public int Id {get; set;}
}
public class SampleContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>().HasKey(c => c.Id);
modelBuilder.Entity<B>().HasKey(c => c.Id);
modelBuilder.Entity<A>().HasRequired(c => c.B1).WihOptional().ForeignKey(x=>x.B1_Id);
modelBuilder.Entity<A>().HasRequired(c => c.B2).WihOptional().ForeignKey(x=>x.B2_Id);
}
}
If "v-Next" is Entity Framework 6, then no, it apparently won't support one-to-one foreign key associations, as you can see on the roadmap for all features planned for EF 6.
You can also see that Unique Constraint support is not on the roadmap and still marked as "Under Review" on UserVoice.
Because a one-to-one foreign key association is basically a one-to-many association with a unique constraint on the foreign key column I would expect that one-to-one FK associations won't be implemented before Unique Constraint support is available. It's especially required if you want that A is the principal in your two relationships. Currently EF does not support relationships where the principal's key is not the primary key but some column with unique constraint.
In this blog post the feature is described and mentioned that it is "postponed", so let's hope for EF 7.
Perhaps it is a terminology issue.
In Code first EF, EF doesnt allow you to have 1:1 relationships with Principal and Dependent both with foreign keys to each other
or with the dependent having its own primary key unrelated to Principal.
With your example it looks like that it is a case of 2 navigation properties required.
And strictly speaking it is not 1:1. since you have 2 relationships to the same table.
you have 2 relationships of type 1:1.. EF sees this as many to 1.
If you have a true 1:1 relationship, EF will want the dependent to have the same Primary Key as the primary.
You can define Multiple NAVIGATION properties on Both Principle and dependent, which result in indexes.
So you may wish to investigate Many to 1 configurations
If you want the Primary to have an OPTINAL Foreign Key at DB level, You would need to ADD this FK later during migration or with script.
But arguably this is best seen as business logic/rule check rather than an OPTIONAL FK on principal.
So yes there are limitations in matching exactly what is possible on the DB.
But it is questionable is actually necessary in a code first scenario.
Neat trick here btw is to model in DB exactly what you want on Code first.
There use the EF Powertool nuget to reerse engineer Codefirst from DB.
EG mini DB with just the desired table relationships.
make a new project in Solution. Install Entity Framework Powertools.
Then use right click option in new project to "reverse engineer code first from DB".
It shows how to build that in code first if it can.... :-)
What I think you wanted to achieve... see code sample (sorry if I misunderstood the point your are making) code should execute if NUGET is loaded
using System.Data.Entity;
namespace EF_DEMO
{
class FK121
{
public static void ENTRYfk121(string[] args)
{
var ctx = new Context121();
ctx.Database.Create();
System.Console.ReadKey();
}
}
public class Main
{
public int MainId { get; set; }
public string BlaMain { set; get; }
public int? Sub1Id { set; get; } // Must be nullable since we want to use EF foreign key
public int? Sub2Id { set; get; } // Must be nullable since we want to use EF foreign key
public virtual Sub Sub1 { get; set; } // Reverse navigation
public virtual Sub Sub2 { get; set; } // Reverse navigation
// you may also need
public virtual ICollection<Sub> Subs { get; set; }
}
public class Sub
{
public int SubId { get; set; } // Deliberately DIFFERENT KEY TO MAIN.... not 1:1 so this is possible
public string blasub { set; get; }
public int MainId { set; get; } //set in API , this the FK
public virtual Main Main { get; set; } // van to Principal
}
public class Context121 : DbContext
{
static Context121()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context121>());
}
public Context121()
: base("Name=Demo") { } // webconfig required to match
public DbSet<Main> Mains { get; set; }
public DbSet<Sub> Subs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Main>().HasKey(t => t.MainId)
.HasOptional(t => t.Sub1)
.WithMany()
.HasForeignKey(t=>t.Sub1Id) ; // tell EF the field is in POCO, use this please, otherwise it will create it.
modelBuilder.Entity<Main>()
.HasOptional(t => t.Sub2).WithMany()
.HasForeignKey(t=>t.Sub2Id);
modelBuilder.Entity<Sub>()
.HasKey(t => t.SubId)
.HasRequired(q => q.Main)
.WithMany()
.HasForeignKey(t => t.MainId);
}
}
}
WEBCONFIG....
<connectionStrings>
<add name="Demo" connectionString="Data Source=localhost;Initial Catalog=Demo;Integrated Security=True;MultipleActiveResultSets=True;App=EntityFramework"
providerName="System.Data.SqlClient" />
</connectionStrings>
Explain what problem do you need to resolve? This is sample of one-to-one mapping in EF 5.0
class Program
{
static void Main(string[] args)
{
using (var context = new SampleContext())
{
var mainEntity = new MainEntity();
mainEntity.DetailEntity = new DetailEntity();
context.SaveChanges();
}
}
}
public class SampleContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MainEntity>().HasKey(c => c.Id);
modelBuilder.Entity<DetailEntity>().HasKey(c => c.Id);
modelBuilder.Entity<MainEntity>().HasOptional(c => c.DetailEntity).WithRequired(p => p.MainEntity);
modelBuilder.Entity<DetailEntity>().HasRequired(c => c.MainEntity).WithOptional(p => p.DetailEntity);
}
public virtual DbSet<MainEntity> MainEntities { get; set; }
public virtual DbSet<DetailEntity> DetailEntities { get; set; }
}
public class MainEntity
{
public int Id { get; set; }
public DetailEntity DetailEntity { get; set; }
}
public class DetailEntity
{
public int Id { get; set; }
public MainEntity MainEntity { get; set; }
}

EF 4.1 Code First ModelBuilder HasForeignKey for One to One Relationships

Very simply I am using Entity Framework 4.1 code first and I would like to replace my [ForeignKey(..)] attributes with fluent calls on modelBuilder instead. Something similar to WithRequired(..) and HasForeignKey(..) below which tie an explicit foreign key property (CreatedBySessionId) together with the associated navigation property (CreatedBySession). But I would like to do this for a one to one relationsip instead of a one to many:
modelBuilder.Entity<..>().HasMany(..).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId)
A more concrete example is below. This works quite happily with the [ForeignKey(..)] attribute but I'd like to do away with it and configure it purely on modelbuilder.
public class VendorApplication
{
public int VendorApplicationId { get; set; }
public int CreatedBySessionId { get; set; }
public virtual Session CreatedBySession { get; set; }
}
public class Session
{
public int SessionId { get; set; }
[ForeignKey("CurrentApplication")]
public int? CurrentApplicationId { get; set; }
public virtual VendorApplication CurrentApplication { get; set; }
public virtual ICollection<VendorApplication> Applications { get; set; }
}
public class MyDataContext: DbContext
{
public IDbSet<VendorApplication> Applications { get; set; }
public IDbSet<Session> Sessions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Session>().HasMany(x => x.Applications).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId).WillCascadeOnDelete(false);
// Note: We have to turn off Cascade delete on Session <-> VendorApplication relationship so that SQL doesn't complain about cyclic cascading deletes
}
}
Here a Session can be responsible for creating many VendorApplications (Session.Applications), but a Session is working on at most one VendorApplication at a time (Session.CurrentApplication). I would like to tie the CurrentApplicationId property with the CurrentApplication navigation property in modelBuilder instead of via the [ForeignKey(..)] attribute.
Things I've Tried
When you remove the [ForeignKey(..)] attribute the CurrentApplication property generates a CurrentApplication_VendorApplicationId column in the database which is not tied to the CurrentApplicationId column.
I've tried explicitly mapping the relationship using the CurrentApplicationId column name as below, but obviously this generates an error because the database column name "CurrentApplicationId" is already being used by the property Session.CurrentApplicationId:
modelBuilder.Entity<Session>().HasOptional(x => x.CurrentApplication).WithOptionalDependent().Map(config => config.MapKey("CurrentApplicationId"));
It feels like I'm missing something very obvious here since all I want to do is perform the same operation that [ForeignKey(..)] does but within the model builder. Or is it a case that this is bad practise and was explicitly left out?
You need to map the relationship as one-to-many and omit the collection property in the relationship.
modelBuilder.Entity<Session>()
.HasOptional(x => x.CurrentApplication)
.WithMany()
.HasForeignKey(x => x.CurrentApplicationId)