EF Core self referencing many to many - c#

I have User table and I'd like to add connection called UserFriend between 2 users. I've searched a lot and basicly tried many different solutions and none of them worked. Everytime I get same error:
Introducing FOREIGN KEY constraint 'FK_UserFriends_Users_Friend2Id' on table 'UserFriends' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Here are my models:
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public string EmailAddress { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public virtual ICollection<UserFriend> Friends { get; set; }
public virtual ICollection<UserFriend> FriendOf { get; set; }
}
public class UserFriend
{
public User Friend1 { get; set; }
public Guid Friend1Id { get; set; }
public User Friend2 { get; set; }
public Guid Friend2Id { get; set; }
public bool Confirmed { get; set; }
public DateTime Added { get; set; }
}
And here's code in DataContext:
modelBuilder.Entity<UserFriend>().HasKey(sc => new { sc.Friend1Id, sc.Friend2Id });
modelBuilder.Entity<UserFriend>()
.HasOne(c => c.Friend1)
.WithMany(c => c.FriendOf)
.HasForeignKey(f => f.Friend1Id);
modelBuilder.Entity<UserFriend>()
.HasOne(c => c.Friend2)
.WithMany(c => c.Friends)
.HasForeignKey(f => f.Friend2Id)
.OnDelete(DeleteBehavior.Restrict);

Change your code to below and remove the other lines you have posted.
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public string EmailAddress { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public virtual ICollection<UserFriend> Friends { get; set; }
public virtual ICollection<UserFriend> FriendOf { get; set; }
}
public class UserFriend
{
public User Friend1 { get; set; }
[ForeignKey("Friend1")]
public Guid? Friend1Id { get; set; }
public User Friend2 { get; set; }
[ForeignKey("Friend2")]
public Guid? Friend2Id { get; set; }
public bool Confirmed { get; set; }
public DateTime Added { get; set; }
}
modelBuilder.Entity<User>();
modelBuilder.Entity<UserFriend>();

Related

This db query takes way too long

I have a query for a details-view of a meeting, which has a lot of included related data:
Meeting meeting = await db.Meetings
.Include(a => a.Agenda)
.ThenInclude(s => s.Speakers)
.ThenInclude(u => u.User)
.Include(s => s.Summonings)
.ThenInclude(u => u.User)
.Where(p => p.Id == parsedMeetingId)
.FirstOrDefaultAsync();
I have set up a Stopwatch, and these are the results for the last six runs (SS:MS):
25:23
25:04
24:36
00:03 // What happened here, I really don't know!
25:25
25:06
There is not much data in the db; Only one Meeting with 4 Summonings, an Agenda of 11 AgendaItems, and only 1 Speaker in 1 AgendaItem.
My dev PC is a rather modest AMD laptop with only 8 GB RAM, running Windows 10. At the time of testing, I was only running Visual Studio and Chrome.
(How) can the query or model design be improved?
Where can I look for other bootle necks?
The models:
public class Meeting
{
public Guid Id { get; set; }
public Guid OwnerId { get; set; }
public Guid ModeratorId { get; set; }
public Guid ReportWriterId { get; set; }
public string Title { get; set; }
public List<AgendaItem> Agenda { get; set; }
public List<MeetingSummoning> Summonings { get; set; }
public ApplicationUser Owner { get; set; }
public ApplicationUser Moderator { get; set; }
public ApplicationUser ReportWriter { get; set; }
// Some more properties ...
}
public class AgendaItem
{
public Guid Id { get; set; }
public string Title { get; set; }
public Guid MeetingId { get; set; }
public Meeting Meeting { get; set; }
public List<Speaker> Speakers { get; set; }
// Some more properties ...
}
public class MeetingSummoning
{
public Guid Id { get; set; }
public Guid MeetingId { get; set; }
public Guid UserId { get; set; }
public Meeting Meeting { get; set; }
public ApplicationUser User { get; set; }
}
public class Speaker
{
public Guid Id { get; set; }
public DateTime Requested { get; set; }
public DateTime? Started { get; set; }
public DateTime? Finished { get; set; }
public bool IsCurrent { get; set; }
public Guid? ReplyToSpeakerId { get; set; }
public Speaker ReplyToSpeaker { get; set; }
public Guid AgendaItemId { get; set; }
public AgendaItem AgendaItem { get; set; }
public Guid UserId { get; set; }
public ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser<Guid>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<MeetingSummoning> MeetingSummonings { get; set; }
public List<Meeting> OwnedMeetings { get; set; }
public List<Meeting> ModeratedMeetings { get; set; }
public List<Meeting> ReportedMeetings { get; set; }
// Lots more properties ...
}

How to delete on cascade EF core

I'm trying to delete the entities related to each other when I remove a row, but it isn't deleting the related entities. It is only deleting one entity and not the others.
My model
public class Company
{
public int CompanyId { get; set;}
public string CompanyName { get; set; }
public int CompanySize { get; set; }
public string Branche { get; set;}
public string Description {get; set;}
public Recruiter Recruiter { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Recruiter
{
public int RecruiterId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public int Compensation { get; set; }
public string Education { get; set; }
public string StartDate { get; set; }
public string Type { get; set; }
public string Profession { get; set; }
public string Language { get; set; }
public string Description { get; set; }
public string Hours { get; set; }
public bool Checked { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(post => post.Company)
.WithMany(company => company.Posts)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.SeedDatabase();
}
The call I make. So when I delete a post, I want that all the related entities are being deleted.
public Post DeclinePostRequest(int postId)
{
var request = _dbContext.Posts.Where(post => post.PostId == postId).Include(post => post.Company).ThenInclude(company => company.Recruiter).FirstOrDefault();
if(!request.Checked)
{
_dbContext.Posts.Remove(request);
_dbContext.SaveChanges();
return request;
}
return null;
}
You are deleting the many side of a 1-to-many relationship there. Everything is working as expected.
Try deleting a Company instead.

How to create a third table for Many to Many relationship in Entity Framework Core?

I have two classes:
One is User
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<Subscription> Subscriptions { get; set; }
}
Other is Subscription:
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
As you can see that User has a list of Subscriptions.
Now when using the entity framework code first approach I am getting a table for User which doesn't contain Subscriptions but a new column for User Id is being added to Subscription table. I was expecting to have a third table which contains two columns one with User ID and the other with subscription ID.
How can I achieve this?
From documentation:
Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
So this answer is correct.
I just corrected code a little bit:
class MyContext : DbContext
{
public DbSet<Use> Users { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserSubscription>()
.HasKey(t => new { t.UserId, t.SubscriptionId });
modelBuilder.Entity<UserSubscription>()
.HasOne(pt => pt.User)
.WithMany(p => p.UserSubscription)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserSubscription>()
.HasOne(pt => pt.Subscription)
.WithMany(t => t.UserSubscription)
.HasForeignKey(pt => pt.SubscriptionId);
}
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<UserSubscription> UserSubscriptions{ get; set; }
}
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<UserSubscription> UserSubscriptions{ get; set; }
}
public class UserSubscription
{
public int UserId { get; set; }
public User User { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
}
PS. You don't need use virtual in navigation property, because lazy loading still not available in EF Core.
Create a third middle table named: UserSubscriptions for example.
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public virtual ICollection<UserSubscription> Subscriptions { get; set; }
}
public class Subscription
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class UserSubscription
{
public int ID { get; set; }
public int SubscriptionID { get; set; }
public decimal Price { get; set; }
public int UserID { get; set; }
public virtual User { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
}
Second Solution:
Add reference for Subscription to User and name it CurrentSubscription for example.
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int CurrentSubscriptionID { get; set; }
public virtual Subscription Subscription { get; set; }
}
public class Subscription
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}

Entity Framework not creating extra columns in join table

Can somebody explain why Entity Framework will not create extra columns CurVal and NewVal in join table? It is creating a join table with DeviceFeatureID and UserDeviceID.
public class DeviceFeature
{
[Display(Name = "ID")]
public int DeviceFeatureID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<DeviceType> DeviceTypes { get; set; }
public virtual ICollection<UserDevice> UserDevices { get; set; }
}
public class UserDevice
{
public int UserDeviceID { get; set; }
public int DeviceTypeID { get; set; }
public string UserID { get; set; }
public string DeviceName { get; set;}
public virtual ICollection<DeviceFeature> DeviceFeatures { get; set; }
}
public class UserDeviceFeatureStatus
{
public int UserDeviceID { get; set; }
public int DeviceFeatureID { get; set; }
public virtual UserDevice UserDevice { get; set; }
public virtual DeviceFeature DeviceFeature { get; set; }
public string CurVal { get; set; }
public string NewVal { get; set; }
}
Your many-to-many relationship table is being generated automatically by your Virtual Collections an not by your entity.
You can either include the join entity in the DbContext (but it will probably duplicate the table unless you remove the virtual collections) or you can use the Fluent API to configure the relationship.
To do the later you will need to change your Virtual Collections to the ReleationShip type and add this to your OnModelCreating:
modelBuilder.Entity<UserDeviceFeatureStatus>()
.HasKey(t => new { t.UserDeviceID, t.DeviceFeatureID });
modelBuilder.Entity<UserDeviceFeatureStatus>()
.HasOne(pt => pt.UserDevice)
.WithMany(p => p.DeviceFeatures) // from UserDevice
.HasForeignKey(pt => pt.UserDeviceID);
modelBuilder.Entity<UserDeviceFeatureStatus>()
.HasOne(pt => pt.DeviceFeature)
.WithMany(t => t.UserDevices) //from DeviceFeature
.HasForeignKey(pt => pt.DeviceFeatureID);
In DeviceFeature
public virtual ICollection<UserDeviceFeatureStatus> UserDevices { get; set; }
In UserDevice
public virtual ICollection<UserDeviceFeatureStatus> DeviceFeatures { get; set; }
For more information about EF relationships you can see the docs here.
If you want to make a many-to-many relationship between UserDevice and DeviceFeature through UserDeviceFeatureStatus than you need to have a navigation property from the 2 tables to your "join" table. I understand that you try to navigate directly from UserDevice to DeviceFeature directly and vice-versa. But you won't be able to do that in EF if your relationship table contains other fields than the primary keys of the tables you're trying to link.
You can try this:
public class DeviceFeature
{
[Display(Name = "ID")]
public int DeviceFeatureID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<DeviceType> DeviceTypes { get; set; }
public virtual ICollection<UserDeviceFeatureStatus> UserDeviceFeatureStatuses { get; set; }
}
public class UserDevice
{
public int UserDeviceID { get; set; }
public int DeviceTypeID { get; set; }
public string UserID { get; set; }
public string DeviceName { get; set;}
public virtual ICollection<UserDeviceFeatureStatus> UserDeviceFeatureStatuses { get; set; }
}
public class UserDeviceFeatureStatus
{
public int UserDeviceID { get; set; }
public int DeviceFeatureID { get; set; }
public virtual UserDevice UserDevice { get; set; }
public virtual DeviceFeature DeviceFeature { get; set; }
public string CurVal { get; set; }
public string NewVal { get; set; }
}
So to navigate from UserDevice to DeviceFeature for example, you'll need to navigate to UserDeviceFeatureStatus first then from there to DeviceFeature.

Defining many to many relation in code first entity framework

I have 3 classes in my model as you can see below.
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserName { get; set; }
public ICollection<MartialArtUserProfile> MartialArtUserProfiles { get; set; }
}
[Table("MartialArt")]
public class MartialArt
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string IconPath { get; set; }
public string ImagePath { get; set; }
public ICollection<MartialArtUserProfile> MartialArtUserProfiles { get; set; }
}
public class MartialArtUserProfile
{
public int UserProfileId { get; set; }
public UserProfile UserProfile { get; set; }
public int MartialArtId { get; set; }
public MartialArt MartialArt { get; set; }
}
And I have a configuration class for many to many relationship as below:
public class MartialArtUserProfileConfiguration : EntityTypeConfiguration<MartialArtUserProfile>
{
public MartialArtUserProfileConfiguration()
{
HasKey(a => new { a.MartialArtId, a.UserProfileId });
HasRequired(a => a.MartialArt)
.WithMany(s => s.MartialArtUserProfiles)
.HasForeignKey(a => a.MartialArtId)
.WillCascadeOnDelete(false);
HasRequired(a => a.UserProfile)
.WithMany(p => p.MartialArtUserProfiles)
.HasForeignKey(a => a.UserProfileId)
.WillCascadeOnDelete(false);
}
}
After defining my entities an relation when I try to run Update-Database in Package Manager Console, it says:
One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmEntityType: : EntityType 'MartialArtUserProfile' has no key defined. Define the key for this EntityType.
\tSystem.Data.Entity.Edm.EdmEntitySet: EntityType: EntitySet 'MartialArtUserProfiles' is based on type 'MartialArtUserProfile' that has no keys defined.
What am I doing wrong?
Thanks in advance,
If I understand you are simply trying to create a many to many with a transitive table. If so this is another way to approach this. Use Fluent API to map as below. You can change the UserProfileToMartialArt to whatever you want the table name to be. Instead of creating the MartialArtUserProfile model let EF create the middle ground for you. This also specifies your keys which should get you around the error.
modelBuilder.Entity<UserProfile>()
.HasMany(b => b.MartialArts)
.WithMany(a => a.UserProfiles)
.Map(m => m.MapLeftKey("MartialArtId")
.MapRightKey("UserProfileId")
.ToTable("UserProfileToMartialArt"));
In MartialArts Model put
public IList<UserProfile> UserProfiles { get; set; }
In UserProfile Model put
public IList<MartialArt> MartialArts { get; set; }
Try doing it like this:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserName { get; set; }
[InverseProperty("UserProfiles")]
public IList<MartialArt> MartialArts { get; set; }
}
[Table("MartialArt")]
public class MartialArt
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string IconPath { get; set; }
public string ImagePath { get; set; }
[InverseProperty("MartialArts")]
public IList<UserProfile> UserProfiles { get; set; }
}
In EntityFramework 6.1, you don't need to do any of this - just add collections of the two types to each class and everything falls into place.
public class UserProfile {
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<MartialArt> MartialArts { get; set; }
public UserProfile() {
MartialArts = new List<MartialArt>();
}
}
public class MartialArt {
public int Id { get; set; }
public string Name { get; set; }
// *snip*
public virtual ICollection<UserProfile> UserProfiles { get; set; }
public MartialArt() {
UserProfiles = new List<UserProfile>();
}
}

Categories

Resources