Create a user relationship table - entity-framework

I want to create a user relationship table in ASP.NET Core and encounter some problems. If I have to disable cascade delete because of this, how do I prevent orphans?
Error:
Introducing FOREIGN KEY constraint 'FK_UserRelationships_AspNetUsers_User2Id' on table 'UserRelationships' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
ApplicationDbContext.cs:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserRelationship>().HasKey(x => new { x.User1Id, x.User2Id });
}
public DbSet<UserRelationship> UserRelationships { get; set; }
}
My current model:
public class UserRelationship
{
public byte RelationshipType { get; set; }
public ApplicationUser User1 { get; set; }
public string User1Id { get; set; }
public ApplicationUser User2 { get; set; }
public string User2Id { get; set; }
}

You have your ApplicationUsers as required so EF will cascade the delete by default twice to the same class (see here).
You need to tell it not to:
builder.Entity<UserRelationship>().HasOne(ur => ur.User1).WithMany().HasForeignKey(ur => ur.UserId1).WillCascadeOnDelete(false);
builder.Entity<UserRelationship>().HasOne(ur => ur.User2).WithMany().HasForeignKey(ur => ur.UserId2).WillCascadeOnDelete(false);

Related

EF-Core - cascade delete - The DELETE statement conflicted with the REFERENCE constraint

When deleting a UserGroup the following exception is thrown.
SqlException: The DELETE statement conflicted with the REFERENCE
constraint "FK_SecurityAssignments_UserGroups_UserGroupId". The
conflict occurred in database "AppDb", table
"dbo.SecurityAssignments", column 'UserGroupId'. The statement has
been terminated.
I've followed the approach from this post, but apparantly missed something ;-)
Cascade deleting with EF Core
The models :
public class UserGroup
{
public int Id { get; set; }
public virtual ICollection<SecurityAssignment> SecurityAssignments { get; set; } = new List<SecurityAssignment>();
}
public class Role
{
public int Id { get; set; }
public virtual ICollection<SecurityAssignment> SecurityAssignments { get; set; } = new List<SecurityAssignment>();
}
public class SecurityAssignment
{
public int Id { get; set; }
public int UserGroupId { get; set; }
public virtual UserGroup UserGroup { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
}
Customisation in OnModelCreating
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//Remove cascade delete unless explicit set below
var cascadeFKs = builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetForeignKeys())
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
foreach (var fk in cascadeFKs)
{
fk.DeleteBehavior = DeleteBehavior.Restrict;
}
// explicit set cascade delete
// Remove security assignments when group deleted
builder.Entity<UserGroup>().HasMany(x => x.SecurityAssignments).WithOne(x => x.UserGroup).HasForeignKey(x => x.UserGroupId).OnDelete(DeleteBehavior.Cascade);
}
Any suggestions ?
The above code and configuration is correct. The problem was that I added the cascade-delete changes but did NOT created a migration.
!!! When changing the model by updating the OnModelCreated, DO NOT FORGET to create a migration !!!

Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints

I am facing the following issue in entity framework core.
Introducing FOREIGN KEY constraint 'FK_MasterCountries_MasterLanguages_LanguageId' on table 'MasterCountries' 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 or index. See previous errors.
I have the following three entities:
1. MasterCountry
public class MasterCountry
{
public long Id { get; set; }
public string Name { get; set; }
public long CurrencyId { get; set; }
public int LanguageId { get; set; }
public bool IsActive { get; set; }
// Navigation Properties
public MasterCurrency MasterCurrency { get; set; }
public MasterLanguage MasterLanguage { get; set; }
}
2. MasterCurrency
public class MasterCurrency
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
// Navigation Property
public MasterCountry MasterCountry { get; set; }
}
3. MasterLanguage
public class MasterLanguage
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
// Navigation Property
public ICollection<MasterCountry> MasterCountries { get; set; }
}
The DbContext class looks like the following:
public partial class NewProjectDbContext : DbContext
{
public DbSet<MasterLanguage> MasterLanguages { get; set; }
public DbSet<MasterCurrency> MasterCurrencies { get; set; }
public DbSet<MasterCountry> MasterCountries { get; set; }
public YouTemaDbContext(DbContextOptions<YouTemaDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder builder)
{
builder
.ApplyConfiguration(new MasterCurrencyConfiguration());
builder
.ApplyConfiguration(new MasterLanguageConfiguration());
builder
.ApplyConfiguration(new MasterCountryConfiguration());
}
}
The configuration classes look like the following:
1. MasterCountryConfiguration
public class MasterCountryConfiguration : IEntityTypeConfiguration<MasterCountry>
{
public void Configure(EntityTypeBuilder<MasterCountry> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Id).UseIdentityColumn();
builder.Property(m => m.Name).IsRequired().HasMaxLength(250).IsUnicode();
builder.Property(m => m.IsActive).IsRequired().HasDefaultValue(0);
builder.Property(m => m.LanguageId).IsRequired();
builder.Property(m => m.CurrencyId).IsRequired();
builder
.HasOne(m => m.MasterLanguage)
.WithMany(a => a.MasterCountries)
.HasForeignKey(m => m.LanguageId);
builder
.HasOne(m => m.MasterCurrency)
.WithOne(a => a.MasterCountry)
.HasForeignKey<MasterCountry>(m => m.CurrencyId);
builder.ToTable("MasterCountries");
}
}
2. MasterCurrencyConfiguration
public class MasterCurrencyConfiguration : IEntityTypeConfiguration<MasterCurrency>
{
public void Configure(EntityTypeBuilder<MasterCurrency> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Id).UseIdentityColumn();
builder.Property(m => m.Name).IsRequired().HasMaxLength(250).IsUnicode();
builder.Property(m => m.IsActive).IsRequired().HasDefaultValue(0);
builder.ToTable("MasterCurrencies");
}
}
3. MasterLanguageConfiguration
public void Configure(EntityTypeBuilder<MasterLanguage> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Id).UseIdentityColumn();
builder.Property(m => m.Name).IsRequired().HasMaxLength(250).IsUnicode();
builder.Property(m => m.IsActive).IsRequired().HasDefaultValue(0);
builder.ToTable("MasterLanguages");
}
}
But while running the migration file, I get the following issue for MasterCurrency and MasterLanguage entities.
CONSTRAINT [FK_MasterCountries_MasterCurrencies_CurrencyId] FOREIGN KEY ([CurrencyId]) REFERENCES [MasterCurrencies] ([Id]) ON DELETE CASCADE
CONSTRAINT [FK_MasterCountries_MasterLanguages_LanguageId] FOREIGN KEY ([LanguageId]) REFERENCES [MasterLanguages] ([Id]) ON DELETE CASCADE
I know that I have to set Cascade delete to No Action but since I am new to entity framework core so I am not sure where I have to do this.
Your help on this will be highly appreciated.
Thanks.

fluent API on table that has relation with itself

I've created a table that has a relation with itself the table has a one-to-many relationship here is my Entity:
public class Permission
{
[Key]
public int PermissionId { get; set; }
public string PermissionTitle { get; set; }
public int? ParentId { get; set; }
#region Relations
[ForeignKey("ParentId")]
public virtual ICollection<Permission> Permissions { get; set; }
#endregion
}
but when I used migration to create the table in SQL, update-database failed for this error:
Introducing FOREIGN KEY constraint 'FK_Permission_Permission_ParentId' on table 'Permission' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
So I decided to use fluent API to solve this issue but I don't know how to Specify ON DELETE NO ACTION by Fluent API on a table that has a relation with itself. any help?
is there any solution to solve my problem?
For EF Core the Entity should normally have two Navigation Properties, like this:
public class Permission
{
[Key]
public int PermissionId { get; set; }
public string PermissionTitle { get; set; }
public int? ParentPermissionId { get; set; }
#region Relationships
public virtual Permission ParentPermission { get; set; }
public virtual ICollection<Permission> ChildPermissions { get; } = new HashSet<Permission>();
#endregion
}
And configured like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Permission>()
.HasMany(p => p.ChildPermissions)
.WithOne(p => p.ParentPermission)
.HasForeignKey(p => p.ParentPermissionId)
.OnDelete(DeleteBehavior.Restrict);
base.OnModelCreating(modelBuilder);
}

how to configure cascade delete with EF Fluent API

I have the following classes and EF convention created the tables fine - all good. Now I want to configure cascade delete on the ToDoList (so that when I delete a ToDoList, all the ToDoItems and UserToDoLists also gets deleted automatically). Must I re-specify what EF convention is able to detect in order to set WillCascadeOnDelete? How do I specify this relationship in fluent API?
public class ToDoList
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<ToDoItem> ToDoItems { get; set; }
public virtual ICollection<UserToDoList> UserToDoLists { get; set; }
}
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
: base()
{
}
public virtual ICollection<UserToDoList> UserToDoLists { get; set; }
}
// a todolist can have many users and a user can have many todolist
// junction table will have addition fields
public class UserToDoList
{
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual int ToDoListId { get; set; }
public virtual ToDoList ToDoList { get; set; }
// additional fields
public bool IsOwner { get; set; }
public int Ordinal { get; set; }
public bool AllowEdit { get; set; }
}
public class UserToDoListConfig : EntityTypeConfiguration<UserToDoList>
{
public UserToDoListConfig()
{
HasKey(ut => new { ut.UserId, ut.ToDoListId });
}
}
public class ToDoListConfig : EntityTypeConfiguration<ToDoList>
{
public ToDoListConfig()
{
Property(t => t.Description).IsRequired().HasMaxLength(50);
HasMany(t =>t.UserToDoLists).??
}
}
That's a simple one-to-many relationship from ToDoList to your junction table UserToDoList, so you just have to set up the mapping like this
HasMany(m => m.UserToDoLists)
.WithRequired(m => m.ToDoList)
.HasForeignKey(m => m.ToDoListId);
and EF will handle your cascading deletes automatically, because ToDoList is required on the junction table side.

Navigation Properties using two foreign keys

I have a project with several tables in the same database.
public class UserImage
{
[Key]
public int Id { get; set; }
public string OwnerId { get; set; }
[ForeignKey("OwnerId")]
public virtual ApplicationUser Owner { get; set; }
//some fields are removed for brevity
}
public class FriendRequest
{
[Key]
public int Id { get; set; }
public string UserId { get; set; }
public string FutureFriendUserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
[ForeignKey("FutureFriendUserId")]
public virtual ApplicationUser FutureFriendUser { get; set; }
}
public class ApplicationUser : IdentityUser
{
//some fields are removed for brevity
public virtual ICollection<UserImage> UserImages { get; set; }
public virtual ICollection<FriendRequest> FriendRequests { get; set; }
The problem is that I can find the images that belong to a user:
var userStore = new UserStore<ApplicationUser>(db);
var userManager = new UserManager<ApplicationUser>(userStore);
ApplicationUser user = userManager.FindById(User.Identity.GetUserId());
IEnumerable<string> imgs = (from image in user.UserImages select Url.Content(image.ImageUrl)).Skip(skip).Take(5).ToList();
but I can't use the same technique for the FriendRequests. If I search in the database for the rows that have UserId == User.Identity.GetUserId() or some other id, the results are what I expect.
What is the problem?
What you're essentially creating here is a self-referential many-to-many relationship. On your FriendRequest class, you have two properties that are foreign keys to ApplicationUser, but on your ApplicationUser class, you have only a single collection of FriendRequest. Entity Framework has no idea which foreign key should actually compose this collection. As a result, you have to make a few changes to get this working properly.
You must add another navigation property. Essentially, on your ApplicationUser class you'll end up with something like the following:
public virtual ICollection<FriendRequest> SentFriendRequests { get; set; }
public virtual ICollection<FriendRequest> ReceivedFriendRequests { get; set; }
Again, you need a collection for each foreign key.
You'll need to add some fluent config to help Entity Framework determine which foreign key to use for each collection:
public class ApplicationContext : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<FriendRequest>().HasRequired(m => m.User).WithMany(m => m.SentFriendRequests);
modelBuilder.Entity<FriendRequest>().HasRequired(m => m.FutureFriendUser).WithMany(m => m.ReceivedFriendRequests);
}