EF CascadeOnDelete Code First - c#

I have this entity:
public class Account
{
[Key]
[ForeignKey("Company")]
[Required]
public Guid CompanyId { get; set; }
public virtual Company Company { get; set; }
}
And this one:
public class Company : PrimaryKey
{
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual Account Account { get; set; }
}
How do I use fluent api to enable cascade delete, I tried this:
modelBuilder.Entity<Company>().HasOptional<Account>().WithRequired().WillCascadeOnDelete();
But I have no idea what this means. Basically, I want a Company to have an optional Account which will be deleted when the company is deleted.

The mapping you need is:
modelBuilder.Entity<Company>()
.HasOptional(c => c.Account)
.WithRequired(a => a.Company)
.WillCascadeOnDelete();
It's a one-to-one relationship between Company and Account. With this mapping you can remove the [ForeignKey("Company")] attribute and the [Required] attribute anyway because a Guid is not nullable and therefore always required.

Please folow the link Enabling Cascade Delete.
You can configure cascade delete on a relationship by using the
WillCascadeOnDelete method. If a foreign key on the dependent entity
is not nullable, then Code First sets cascade delete on the
relationship. If a foreign key on the dependent entity is nullable,
Code First does not set cascade delete on the relationship, and when
the principal is deleted the foreign key will be set to null.
You can remove these cascade delete conventions by using:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()
The following code configures the relationship to be required and then
disables cascade delete.
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
something like it in your example:
modelBuilder
.Entity<Company>()
.HasOptional<Account>()
.HasForeignKey(a => a.CompanyId)
.WillCascadeOnDelete();

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 fluent API c# cascade delete issue

I am having problems configuring a model to not cascade delete.
The modeL.
public class Hit
{
public int Id { get; set; }
// Foreign Key
public int AccountId { get; set; }
// Foreign Key
public int LeadId { get; set; }
//navigation properties
[ForeignKey("LeadId")]
public Lead lead { get; set; }
//navigation properties
[ForeignKey("AccountId")]
public Account account { get; set; }
}
Fluent API:
modelBuilder.Entity<Hit>()
.HasRequired(t => t.lead)
.WithMany()
.HasForeignKey(t => t.LeadId)
.WillCascadeOnDelete(false);
When I try to migrate it, i get this in create table on Hit:
.ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
.ForeignKey("dbo.Leads", t => t.Lead_Id)
.ForeignKey("dbo.Leads", t => t.LeadId, cascadeDelete: true)
What I want to achieve for both leads and accounts is this in the migration script:
.ForeignKey("dbo.Leads", t => t.LeadId)
How should I go about doing this? Its prbably easy, but I am new to all this:(
EDIT 1:
I added this to the fluent API:
modelBuilder.Entity<Lead>().HasMany(i => i.Hits).WithOptional().HasForeignKey(s=> s.LeadId).WillCascadeOnDelete(false);
Which remove cascadelete from: .ForeignKey("dbo.Leads", t => t.LeadId)
However, I also want to remove it from:
.ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
The easy solution would be to remove it from the migration script. But I would like how to do this with FLuent API or other methods.
Any ideas?
Like I already said in the comments above, if you use Fluent API for mapping your FKs remove the [ForeignKey] attributes from your navigation properties.
You can then do non-cascading mappings like this:
modelBuilder.Entity<Hit>().HasOptional(m => m.account).WithMany().HasForeignKey(m
=> m.AccountId).WillCascadeOnDelete(false);
Finally you have to check if your FK columns should be optional or required, for optional mappings change your FK properties AccountId and LeadId to data type int?, otherwise you have to change the HasOptional() part of your FK mappings to HasRequired().
Define your model like so:
public class Hit
{
public int Id { get; set; }
public virtual Lead Lead { get; set; }
public virtual Account Account { get; set; }
}
The virtual keyword will let Entity Framework know you are defining a foreign key and will match on Id automatically.
Remove the HasRequired(t => t.lead).WithMany() part from Fluent API. If a property is required it cannot exist without it and will cascade on delete.

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.

Can't map one to zero or one relation

I am using code first to generate tables.
I have User object:
public class ApplicationUser
{
public int? ImageId { get; set; }
public virtual Image Image { get; set; }
public virtual ICollection<Image> Images { get; set; }
and class Image:
public class Image
{
public int ImageId { get; set; }
public int CreatedBy { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
I map objects via fluent api:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// User can create many images
modelBuilder.Entity<Image>()
.HasRequired(e => e.CreatedByUser)
.WithMany(e => e.Images)
.HasForeignKey(e => e.CreatedBy)
.WillCascadeOnDelete(false);
// User can and doesn't have to have image
modelBuilder.Entity<ApplicationUser>()
.HasRequired(e => e.Image)
.WithOptional()
.WillCascadeOnDelete(false);
The first part create me one relation UserId > CreatedBy fine.
But second relation is 1:1 and related fields are UserId > ImageId which is not what I am trying to make.
I have tried to use HasOptional instead HasRequired but then I get additional keys in tables.
What should I do to map this two tables?
UPDATE 1
Based on answer. I leave User and Image classes the same.
User have One image (for profile) and list of images (all other images that user created).
And I use fluent api to connect tables:
But EF generate me additional key and doesn't use User > ImageId as key I can't understand why?
modelBuilder.Entity<ApplicationUser>()
.HasOptional(e => e.Image)
.WithOptionalDependent()
.WillCascadeOnDelete(false);
Unfortunately, EF does not support 1:1 mappings in this way. If you think about it, the reason should be obvious. How would you model this in a database? You can't. The best you can do is create dual 1:many and many:1 connections.
ie, if you have two ApplicationUser rows, they could both have the same ImageId. There's no way to guarantee that there is only one row (at least not without constraints, which EF doesn't support).
EF only supports 1:1 when using a shared primary key. That means both entities have to use the same Key name, and they both have to be primary keys, and one has to also make the other a Foreign key.

entity framework multiple cascade paths error

What I've done so far to achieve what I want using Entity Framework is something like this:
// User.cs
public class User {
public Guid ID { get; set; } // column: user_id
public virtual ICollection<Event> Events { get; set; }
}
// Event.cs
public class Event {
public Guid ID { get; set; } // column: event_id
public virtual Guid UserID { get; set; } // column: event_userid
public virtual ICollection<User> Guests { get; set; }
}
// MyAppContext.cs
...
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<User>()
.HasKey(u => u.ID)
.HasMany(u => u.Events)
.WithOptional()
.HasForeignKey(e => e.UserID);
mb.Entity<Event>()
.HasKey(e => e.ID)
.HasMany(e => e.Guests)
.WithMany();
}
...
I was expecting the database structure to be as follows:
TABLE: user
user_id uniqueidentifier not null primary key
TABLE: event
event_id uniqueidentifier not null primary key
event_userid uniqueidentifier not null foreign key references user(user_id)
TABLE: event_guests
event_id uniqueidentifier not null
user_id uniqueidentifier not null
I have a feeling that the fluent API I'm using above is not going to give the expected database structure and also, I get the following exception that I've no clue how to fix:
Introducing FOREIGN KEY constraint 'FK_xxx' on table 'event_guests'
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'm new to entity framework, any help would be much appreciated.
Try replacing your configurations with a single many to many configuration.
modelBuilder.Entity<User>()
.HasMany(a => a.Events)
.WithMany(b=> b.Guests)
.Map(x =>
{
x.MapLeftKey("UserId");
x.MapRightKey("EventId");
x.ToTable("EventGuests");
});

Categories

Resources