Cascade delete (potentially null) collection with EF CodeFirst - c#

I have this class:
public class Participant
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public virtual PersonName Name { get; set; }
public virtual ICollection<ResponseSet> SurveyResponses { get; set; }
}
When the participant is first created there won't be any survey responses (so I don't think the answer is to make this collection required). However, I'd like to have a cascading delete on all ResponeSets in SurveyResponses if I delete a Participant. I added this to my DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Participant>()
.HasOptional(d=>d.SurveyResponses)
.WithMany()
.WillCascadeOnDelete();
}
But this doesn't quite work. Yes, it has created cascading deletes. But it's just added a SurveyResponses_Id FK on the Participants table. Unless I'm missing something that's not what I'm trying to do because the Participant can have more than 1 ResponseSet in the collection.
Unfortunately, I've found a lot of answers to this question but none of them seem to deal with collections.

First the relationship between Person and Response Surveys is 1:n. Because that the foreign key of person must be in the Response survey table. The collection Survey responses is just a navigation property and not have really a column representation in the database.
modelBuilder.Entity<ResponseSet>()
.HasRequired(s =>s.Participant)
.WithMany(s =>s.SurveyResponses)
.Map(s =>s.MapKey("ParticipantId"))
.WillCascadeOnDelete(true);

Related

Entity Framework : Invalid column name *_ID even with fluent api or data annotations

Odd issue that I've been looking at all day. I am working with Entity Framework 6. The issue I have is that I have three entities:
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
// other properties and relationships here
public int? OrderProfileId { get; set; }
public int OrderTemplateId { get; set; }
public virtual OrderProfile Profile { get; set; } // optional property
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderProfile
{
public int Id { get; set; }
// other properties
// added here 6/15/2021
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderTemplate : EntityMetaData
{
public int Id { get; set; }
// other properties
public int? OrderProfileId{ get; set; }
public OrderProfile OrderProfile { get; set; }
}
In our model builder, we have these definitions:
modelBuilder.Entity<Order>()
.HasOptional(x => x.OrderProfile)
.WithMany(x => x.Orders)
.HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
But even with the above fluent api model, we get the error
Invalid column name 'OrderProfile_Id'
Throughout various testing I was unable to find why this issue was occurring, so I looked at our logs and found when this error started popping it's head up and then was able to find the changes associated to OrderProfile and found that the only change that was made was adding the relationship from OrderProfile to OrderTemplate.
When I removed that fluent api relationship OrderProfile to OrderTemplate, it worked as expected... I don't need that relationship to OrderTemplate, but would like it to be there, how can I establish a optional 1 to optional 1 relationship without breaking other relationships? Also, why would additional relationships be effected by this?
UPDATE 6/15/2021
So I found I had a reverse navigation property in the OrderProfile model:
public virtual OrderTemplate OrderTemplate{ get; set; }
removing that and the associated fluent relationship
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
Doing the above resolved the issue, but for some reason, the issue seems to have cascaded down to another relationship that has a circular reference like the above. The Order class is involved with this cascaded issue. I guess this is a pretty big cause for concern since this application worked fine for the last 4 years and for these relationships to be decaying like this is worrisome. Does anyone know why this is happening?
if you use the right naming convention, EF will do magic. in this sample, you don't need fluent API to relate entities.
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
public int? OrderProfileId { get; set; } //means HasOptional (nullable) and ForeignKey
//variable name must be OrderProfile not Profile
public virtual OrderProfile OrderProfile { get; set; }
}
public class OrderProfile
{
public OrderProfile()
{
Orders = new HashSet<Order>();
}
public int Id { get; set; }
//be aware circular reference at any conversion or mapping
public virtual ICollection<Order> Orders {get; set;} //means WithMany
}
I've got an error like this too. It's caused by unmatching OrderProfileId property in OrderTemplate class with the fluent api model
If I'm not wrong, you want the OrderProfile model a many to many relation between Order and OrderTemplate. Then if it was the case, add the nvaigation property in OrderProfile.
public class OrderProfile
{
public int Id { get; set; }
// other properties
public virtual ICollection<Order> Orders { get; set; }
public virtual OrderTemplate OrderTemplate { get; set; }
}
Then change the fluent api model to be like this
// the EF has modelled the relation for normal 1 to many relation
// modelBuilder.Entity<Order>()
// .HasOptional(x => x.OrderProfile)
// .WithMany(x => x.Orders)
// .HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderTemplate>()
.HasOptional(x => x.OrderProfile)
.WithOptional(x => x.OrderTemplate);
You're working database-first, which always leaves room for a mismatch between the actual database model and the model EF infers from class and property names and mapping code (= conceptual model). If this happens, it may help to make EF generate a database from the conceptual model and see where it creates the column it expects, OrderProfile_Id.
This is what you'll see when logging the SQL statements:
CREATE TABLE [dbo].[OrderTemplates] (
[Id] [int] NOT NULL IDENTITY,
[OrderProfileId] [int],
[OrderProfile_Id] [int],
CONSTRAINT [PK_dbo.OrderTemplates] PRIMARY KEY ([Id])
)
...
ALTER TABLE [dbo].[OrderTemplates]
ADD CONSTRAINT [FK_dbo.OrderTemplates_dbo.OrderProfiles_OrderProfile_Id]
FOREIGN KEY ([OrderProfile_Id]) REFERENCES [dbo].[OrderProfiles] ([Id])
There you see the expected nullable column OrderProfile_Id which is the FK to OrderProfiles. It's noteworthy to see that EF does not use OrderProfileId as a foreign key field. It's just a field that could be used for anything.
That's because EF6 doesn't support 1:1 associations as foreign key associations (reference property and primitive FK property).
Knowing this, the remedy is simple: remove the property OrderTemplate.OrderProfileId and tell EF to use the field OrderTemplate.OrderProfileId in the database:
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile)
.Map(m => m.MapKey("OrderProfileId"));
That said, I wonder why Order has a foreign key to OrderProfile. Isn't its OrderProfile determined by its OrderTemplate? If it's a redundant relationship it may be better to remove it.

EF re-adding FK columns after adding ICollection navigation property

I have the following classes, that already have their tables created and defined using EF migrations:
[Table("Account")]
public class AccountEntity
{
[Key]
public virtual int Id { get; set; }
}
[Table("Request")]
public class RequestEntity
{
[Key]
public virtual int Id { get; set; }
public int? AccountID { get; set; }
[ForeignKey("AccountID")]
public virtual AccountEntity Account { get; set; }
}
In the Request table, this properly created the FK FK_dbo.Request_dbo.Account_AccountID
Using SSMS, I can confirm the FK is setup properly.
In order to be able to access the Request's one-to-many property from the Account entity, I added the following property to the AccountEntity class:
public virtual ICollection<RequestEntity> Requests { get; set; }
However, now EF suspects that I need to run migrations due to domain changes.
Here's the migration class that it creates, and wants to run:
public partial class RequestUpdate : DbMigration
{
public override void Up()
{
AddColumn("dbo.Request", "AccountEntity_Id", c => c.Int());
CreateIndex("dbo.Request", "AccountEntity_Id");
AddForeignKey("dbo.Request", "AccountEntity_Id", "dbo.Account", "Id");
}
public override void Down()
{
DropForeignKey("dbo.Request", "AccountEntity_Id", "dbo.RealtorAccount");
DropIndex("dbo.Request", new[] { "AccountEntity_Id" });
DropColumn("dbo.Request", "AccountEntity_Id");
}
}
As you can see, EF seems to not recongize/respect that the FK relationshp has already been setup.
I don't suspect that any migrations need to be setup. The FK is already established, and I'm simply adding the collection "navigation" property.
Migrations need to be enabled for this project. EF version is 6, .NET 4.5.
One possible way this can happen is if you have used fluent configuration like this:
modelBuilder.Entity<RequestEntity>()
.HasOptional(e => e.Account)
.WithMany();
and forgot to update the .WithMany() to .WithMany(e => e.Requests) after introducing the collection navigation property, in which case EF considers two one-to-many relationships, hence adds a second FK column with default name.

How do I configure Entity Framework cascade delete to work properly with a one to many relationship?

After researching cascade deletes and browsing issues here, I'm under the impression that the following scenario will work
Entity with many:
public partial class master
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public master()
{
analysis = new HashSet<analysis>();
}
[Key]
public int id { get; set; }
[StringLength(50)]
public string description { get; set; }
public virtual ICollection<analysis> analysis { get; set; }
}
Entity with one:
public partial class analysis
{
[Key]
public int id { get; set; }
[StringLength(50)]
public string description { get; set; }
public int? master_id { get; set; }
public virtual master master { get; set; }
}
The foreign key on analysis entity is nullable, so that cascade deletion will automagically set the FK to null when it tries to delete the master record.
That configuration is in my Context:
public class Context : DbContext
{
public Context() : base("Context")
{
this.Configuration.LazyLoadingEnabled = true;
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
}
public virtual DbSet<master> master { get; set; }
public virtual DbSet<analysis> analysis { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<master>()
.HasMany(e => e.analysis)
.WithOptional(e => e.master)
.HasForeignKey(e => e.master_id)
.WillCascadeOnDelete(true);
}
}
Then when I attempt to delete a master record by passing the id into the following (generic repo snippet, this works fine when deleting a master record with no analyses):
public void Delete(int id)
{
var entity = FindByKey(id);
_dbSet.Remove(entity);
_context.SaveChanges();
}
I get an error
Cannot delete or update a parent row: a foreign key constraint fails
on this record. What am I missing here?
The use of CascaseOnDelete should mirror cascade rules on the database. With CodeFirst when you set a relationship as Required, EF maps this both in the mapping and the schema as a delete cascade. With Optional relationships this isn't the case, it assumes deleting a parent will leave the child orphaned since the child accepts an existence without a parent due to it's null-able FK. However, without knowledge of the child records it cannot clear the ID out of the FK or delete the data so it relies on whatever the DB is set up to do.
You can still use .WillCascadeOnDelete(), though for it to work, the context needs to know about the children. For instance, on a new context:
var master = _db.Masters.Find(id);
_db.Masters.Remove(master);
This typically fails with a FK constraint error. (SQL Server) The error message differs from yours so I suspect like Tetsuya that is a different provider?
To resolve this you can use:
var master = _db.Masters.Include(x=>x.analysis).Find(id);
_db.Masters.Remove(master);
though that can be tedious for objects with larger graphs to include all children.
As a general rule if you want to use cascade deletes, you need to ensure that the database schema is set up with a cascade on delete behaviour. With a null-able FK and a cascade delete rule the original code should behave as expected. Alternatively, Setting a cascade rule to "Set to Null" will leave the child records orphaned with a null FK. (no error from EF)

ASP.NET MVC5 - unwanted field created after update database

I'm trying to create a 'one to many' relation between the classes 'ApplicationUser' and one recently created called 'Issue'.
So, in Models / IdentityModels.cs / ApplicationUser i added this property:
public ICollection<Issue> Issues { get; set; }
And Issue.cs has this code:
namespace Test.Models
{
public class Issue
{
public int Id { get; set; }
public ApplicationUser Courier { get; set; }
public ApplicationUser Customer { get; set; }
}
}
I'm using automatic migrations. So, after building and running 'update-database', the Issues table was created with these fields:
Id
ApplicationUser_Id
CourierId
CustomerId
My question is why was the field 'ApplicationUser_Id ' created and how can i prevent it?
The problem is that EF thinks you actually want three one-to-many relationships between Issue and ApplicationUser:
one for ICollection<Issue> Issues on ApplicationUser (ApplicationUser_Id)
one for ApplicationUser Courier on Issue (CourierId)
one for ApplicationUser Customer on Issue (CustomerId)
(Note that EF allows to define relationships from either side.)
If you want that ApplicationUser.Issues contains all Issues of this User, whether he is a Courier or Customer, you will need the additional ApplicationUser_Id key. Configuring EF so that this works will be quite a pain.
Maybe a simpler solution will do: introduce two collections on ApplicationUser.
public ICollection<Issue> CourierIssues { get; set; }
public ICollection<Issue> CustomerIssues { get; set; }
And then configure the backlinks in the ModelBuilder using the fluent API to eliminate the ApplicationUser_Id key:
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CourierIssues).WithOptional(i => i.Courier);
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CustomerIssues).WithOptional(i => i.Customer);
Because you have two foreign keys to ApplicationUser, Courier and Customer, but only one collection referencing Issue on ApplicationUser. EF has no way of know which foreign key it should line up with, so it just created a new one. To handle this properly, you need to utilize fluent config:
public class ApplicationUser
{
...
public class Mapping : EntityTypeConfiguration<ApplicationUser>
{
HasMany(m => m.Issues).WithRequired(m => m.Customer);
}
}
Then, in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ApplicationUser.Mapping());
}
The problem here of course, is that you are likely wanting to track collections for both the Customer and Courier collections. For that, you need two collections:
public virtual ICollection<Issue> CustomerIssues { get; set; }
public virtual ICollection<Issue> CourierIssues { get; set; }
Then, the following fluent config:
HasMany(m => m.CustomerIssues).WithRequired(m => m.Customer);
HasMany(m => m.CourierIssues).WithRequired(m => m.Courier);

ForeignKey - Definition in Many-to-Many (EntityFramework)?

Because I don't like the naming convention the entityFramework (Code first) uses I usually use a foreignKey property to assign my own property:
public class User {
public virtual Customer Customer { get; set; }
[ForeignKey("Customer")]
public Guid CustomerId { get; set; }
}
Works just fine, a column "CustomerId" is created inside the "User"-Table.
But how do I achieve the same when I need a many-to-many relationship?
public class User {
public virtual ICollection<Role> Roles { get; set; }
}
public class Role {
public virtual ICollection<User> Users { get;set; }
}
This results in a Table "UserRole" which contains "User_id" and "Role_Id".
Still, I prefer "UserId" and "RoleId". I could just create a UserRole-Class myself containing the references to User and Role but there should be a more elegant way. Is there any?
You could configure that many-to-many relationship on your context using Fluent Api as I show below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany<Role>(u => u.Roles)
.WithMany(c => c.Users)
.Map(cs =>
{
cs.MapLeftKey("UserId");
cs.MapRightKey("RoleId");
cs.ToTable("UserRoles");
});
}
This way you could name the junction table and the FK columns with the names that you want.
Another way is create an entity that represent the junction table and stablish two one-to-many relationships with User and Role, but if you don't need to add extra columns to the junction table, It's recommended use the first variant, but you can find some advantages in map this table explicitly.

Categories

Resources