how to configure cascade delete with EF Fluent API - entity-framework

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.

Related

EF Core Many-to-Many Relation Table Naming [duplicate]

This question already has answers here:
Change name of generated Join table ( Many to Many ) - EF Core 5
(2 answers)
Closed 1 year ago.
Does EF Core provide a way of naming the many-to-many relations mapping to database tables ?
In a code-first pattern, I have the following 2 Entities:
[Table("Prefix.Users")]
public class User
{
public int ID { get; set; }
public IEnumerable<Role> Roles { get; set; }
}
[Table("Prefix.Roles")]
public class Role
{
public int ID { get; set; }
public IEnumerable<User> Users { get; set; }
}
I've skipped the detailed Entity structure here. The ID properties in User & Role are keys (Database generated Identity)
User and Role entities share a many-to-many relationship.
EF Core generates a third table in Database with Table name UsersRoles
Is there a way I can add a prefix to the 3rd table name so it becomes Prefix.UsersRoles without manually adding a third Entity UserRoles that maps User and Role and giving it the desired name with Prefix
Use fluent API instead of using data annotations
Your model classes should be like this.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
Your fluent api configuration classes like be this
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("User");
builder.HasKey(x => x.Id);
}
}
public class RoleConfiguration : IEntityTypeConfiguration<Role>
{
public void Configure(EntityTypeBuilder<Role> builder)
{
builder.ToTable("Role");
builder.HasKey(x => x.Id);
}
}
public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole>
{
public void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.ToTable("UserRole");
builder.HasKey(x => new { x.UserId, x.RoleId });
builder
.HasOne<Role>(s => s.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(s => s.RoleId).OnDelete(DeleteBehavior.Restrict);
builder
.HasOne<User>(s => s.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(s => s.UserId).OnDelete(DeleteBehavior.Restrict);
}
}
Your DbContext class should be like this
public class MyDbContext : DbContext
{
public EEGDbContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(#"Server=xxxx;Database=DB;User Id=sa;Password=xxxxx;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
}

Entity Framework Core shared table with cascade delete

I try to create the following database design with EF Core (code-first)
Entity "Recipe" can have a list of type "Resource"
Entity "Shop" can have a single "Resource"
Entity "InstructionStep" can have a list of type "Resource"
If I delete a resource from the "Recipe", "InstructionStep" (collections) or from the "Shop" (single-property) then the corresponding "Resource" entity should be also deleted. (Cascade Delete)
I already tried several things with and without mapping tables but none of my approach was successful.
Another idea was to have a property "ItemRefId" in the "Resource" entity to save the "RecipeId/ShopId/InstructionStepId" but I don't get it to work...
Example Classes:
public class Recipe
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
public class Shop
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class InstructionStep
{
public string InstructionStepId { get; set; }
public string Title { get; set; }
public ICollection<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
Any suggestions? Many thanks in advance.
That's not cascade delete. Cascade delete would be when a Recipe is deleted, all of the related Resources are deleted as well.
In EF Core 3, you can use Owned Entity Types for this. The generated relational model is different from what you are proposing, in that Recipe_Resource and InstructionStep_Resource will be seperate tables, and Shop.Logo will be stored in columns on the Shop table. But that's the correct relational model. Having one Resource table with some rows referencing a Recipe and some rows referencing an InstructionStep is a bad idea.
This scenario is sometimes called a "Strong Relationship" where the identity of the related entity is dependent on the main entity, and should be implemented in the relational model by having the the Foreign Key columns be Primary Key columns on the dependent entity. That way there's no way remove a Recipe_Resource without deleting it.
eg
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
namespace EfCore3Test
{
public class Recipe
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Shop
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class InstructionStep
{
public string InstructionStepId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Db : DbContext
{
public DbSet<Recipe> Recipes { get; set; }
public virtual DbSet<Shop> Shops { get; set; }
public virtual DbSet<InstructionStep> InstructionSteps { get; set; }
private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information).AddConsole();
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory)
.UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true",
o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Shop>().OwnsOne(p => p.Logo);
modelBuilder.Entity<InstructionStep>().OwnsMany(p => p.Resources);
modelBuilder.Entity<Recipe>().OwnsMany(p => p.Resources);
}
}
class Program
{
static void Main(string[] args)
{
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var r = new Recipe();
r.Resources.Add(new Resource() { ItemRefId = 2, Path = "/" });
db.Recipes.Add(r);
db.SaveChanges();
r.Resources.Remove(r.Resources.First());
db.SaveChanges();
var s = new Shop();
s.Logo = new Resource { ItemRefId = 2, Path = "/" };
db.Shops.Add(s);
db.SaveChanges();
s.Logo = null;
db.SaveChanges();
}
}
}

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);
}

EF 4.0 - CodeFirst One To Many - Fluent API

I have the following two classes:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
}
public class Trip
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Person> Persons { get; set; }
}
As you can see, a Trip can have 1 or more Persons...
I tried to use the EntityConfiguration to build the database properly but I cannot manage to make it work... I am quite confused on its usage:
public class TripConfiguration : EntityTypeConfiguration<Trip>
{
internal TripConfiguration()
{
// ???
}
}
What do I need to write to have the application to behave properly:
I need at least one person.
I might have more that one person
A person cannot be in the SAME trip twice
A person can be in more than one trip
Try this:
this.HasRequired(x => x.Person)
.WithMany(x => x.Trips)
.HasForeignKey(x => x.PersonId);
Your classes:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
public virtual ICollection<Trip> Trips { get; set;}
}
public class Trip
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
And as far that I know, EF doesn't support unique FK (or correct me if I'm wrong..). So you have to check it yourself.
This is not a One-To-Many relationship, this is a Many-To-Many relationship, you need to have collections on both sides of the relationship. EF will create the joiner table on your behalf. Since today you cannot configure a person being in a trip only once you will need to create a unique constraint in your joiner table once is created to assure this happens since EF does not yet support Unique Key constraints through configuration.
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
public virtual ICollection<Trip> Trips { get; set; }
}
public class Trip
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
then
class PersonConfiguration : EntityTypeConfiguration<Person>
{
public PersonConfiguration()
{
this.HasMany(t => t.Trips).WithMany(t => t.Persons);
}
}

M:M Mapping - EF 4.3 CodeFirst (Existing Database)

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table