EF Core Navigation property on ApplicationUser is always null - entity-framework-core

I'm trying to have a list of "buddies" for an ApplicationUser (just using the standard ASP NET Core Identity implementation).
In my mind, an ApplicationUser can have multiple Buddies, and a Buddy can have multiple ApplicationUser's. This means its a many to many relationship.
In line with EF Core's existing limitations with M2M relationships, I made a join class that looks like this:
public class ApplicationUserBuddies
{
public string UserGUID { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string BuddyGUID { get; set; }
public ApplicationUser BuddyUser { get; set; }
}
I then configure ModelBuilder to recognise this relationship with in my OnModelCreating override
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasOne(x => x.ApplicationUser)
.WithMany(x => x.Buddies).HasForeignKey(x => x.UserGUID).IsRequired();
However, when I query through this table using EF, the "buddy" navigation property is always null. The actual string is set correctly (to that users GUID). This is how I am getting those details (note I am using Include):
var buddies = await _context.ApplicationUserBuddies.Include(x => x.BuddyUser).Where(x => x.UserGUID == UserGUID)
.Select(x => x.BuddyUser).ToListAsync();
Another strange thing is in my ApplicationUserBuddies table, I get a third column that I haven't set up anywhere called BuddyUserID
I think the issue I am having is because I have a many to many relationship that references one table (AspNetUsers table) and that could be affecting it.
How can I have a list of users as a navigation property from AspNetUsers and have it work correctly? Thanks everyone.
EDIT
I have changed my model to be in line with EF conventions
public class ApplicationUserBuddies
{
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string BuddyUserId { get; set; }
public ApplicationUser BuddyUser { get; set; }
}
And my model builder now looks like this:
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasKey(x => new { x.UserId, x.BuddyUserId });
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasOne(x => x.ApplicationUser)
.WithMany(x => x.Buddies).HasForeignKey(x => x.BuddyUserId).IsRequired();
And now I have the columns UserId, BuddyUserId, and the erronerous BuddyUserId1. I think that this is essentially the same issue that I had at the outset.

Related

EF Core 3.1.7 Data annotations for multiple 1:1 relationships in table

I am having problems figuring out the data annotations to map more than one 1:1 relationships so that EF Core 3.11.7 understands it and can build a migration.
I have a Person table and a Notes table.
There is a 0:M Notes relationship in Person. A person record can have 0 or more notes.
In the notes table is a CreatedBy field which is a Person. It also has a LastEditedBy field which is also a person. EF keeps bombing on how to construct the relationship for Note.CreatedBy. If this were non EF, both fields would be ints with the PersonID of the proper person record. How do it, preferabbly with Data Annotations, explain this to EF Core?
When I try to create a migration it fails and says:
System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'Note.CreatedBy' of type 'Person'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace VetReg.Domain.Model
{
public class Family
{
public int FamilyID { get; set; } = -1;
public string FamilyName { get; set; }
public List<Pet> Pets { get; set; } = new List<Pet>();
public List<PersonFamily> People { get; set; }
public int AddressID { get; set; } = -1;
public List<Note> Notes { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime? Birthdate { get; set; }
public string Password { get; set; }
public List<PersonFamily> Families { get; set; }
public List<Note> Notes { get; set; }
} // class People
public class Note
{
public int NoteID { get; set; }
public int CreatedByID { get; set; }
[ForeignKey("CreatedByID")]
public Person CreatedBy { get; set; }
public DateTime DateCreated { get; set; }
public int LastEditByID { get; set; }
[ForeignKey("LastEditByID")]
public Person LastEditBy { get; set; }
public DateTime? LastEditDate { get; set; }
public string NoteText { get; set; }
}
public class PersonFamily
{
public int PersonID { get; set; }
public int FamilyID { get; set; }
public Person Person { get; set; }
public Family Family { get; set; }
}
}
The question is (and this is what makes impossible to EF to automatically determine the relationships) what is the relation between Person.Notes and Note.CreatedBy / Note.LastEditBy - none probably? You've said there is 0:M relationship between Person and Note, but note that there are potentially 3 one-to-many relationships there - notes associated with person, notes created by person and notes edited by person, which potentially leads to 3 FKs to Person in Note.
Also note that none of the navigation properties is required, but when present they must be paired.
Assuming you want 3 relationships, i.e. there is no relation between Note.CreatedBy / Note.LastEditBy and Person.Notes, you need to tell EF that Note.CreatedBy and Note.LastEditBy do not have corresponding (a.k.a. inverse) navigation property in Person. This is not possible with data annotations. The only available data annotation for that purpose [InverseProperty(...)] does not accept empty/null string name, hence cannot be used for what is needed here.
Also there is another problem here which you will encounter after resolving the current, which also cannot be resolved with data annotations. Since you have multiple required (thus cascade delete by default) relationships from Person to Note, it creates the famous "cycles or multiple cascade paths" problem with SqlServer, and requires turning off at least one of the cascade delete.
With that being said, the model in question needs the following minimal fluent configuration:
modelBuilder.Entity<Note>()
.HasOne(e => e.CreatedBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Note>()
.HasOne(e => e.LastEditBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
The essential for the original issue are the HasOne / WithMany pairs. Once you do that, EF Core will automatically map the unmapped Person.Notes collection to a third optional relationship with no inverse navigation property and shadow FP property (and column) called "PersonId", i.e. the equivalent of
modelBuilder.Entity<Person>()
.HasMany(e => e.Notes)
.WithOne()
.HasForeignKey("PersonId");
Regarding the second issue with multiple cascade paths, instead of Restrict you can use any non cascading option or the newer ClientCascade. And it could be for just one of the relationships, as soon as it breaks the "cascade path" (apparently you can't break the cycle because it is demanded by the model).

Configuring one-to-many and one-to-one relationship in Entity Framework Core

I'm quite new to Entity Framework and am picking it up with the Core version.
I'm trying to understand how to customise model relationships.
My basic model is that I have a Company entity, and a Contact entity. A Company can have many Contacts. A company can a KeyContact, which must be one of the associated contacts, but is not required.
Thus there is a One to Many relationship, but also a One to One relationship. I've tried to implement this as below (removed most other fields for clarity);
public class Company
{
public int Id { get; set; }
public int? KeyContactId { get; set; }
public ICollection<Contact> Contacts { get; set; }
public Contact KeyContact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public int CompanyId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Company Company { get; set; }
}
It fails to add this migration with the message;
Unable to determine the relationship represented by navigation property 'Company.Contacts' of type 'ICollection'. Either manually configure the relationship, or ignore this property from the model.
I can kinda see why it's complaining about this, but I'm not sure if there's a way with the model builder I can configure this, or whether it's an invalid pattern. My model builder is currently just basic;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>().ToTable("Company");
modelBuilder.Entity<Contact>().ToTable("Contact");
}
I know I could just have a flag to say IsKeyContact in the contact table, but I like the idea of having the navigation property in the company entity. So I'm wondering how sugary Entity can be.
Any help much appreciated.
Thanks,
Nick
The exception is avoided by adding the following line to the OnModelCreating method:
modelBuilder.Entity<Company>().HasMany(p => p.Contacts).WithOne(d => d.Company).HasForeignKey(d => d.CompanyId);
This configures the Company.Contacts-Contact.Company relation. By default, the Company.KeyContact relation is configured as if the following line would be within the OnModelCreating method:
modelBuilder.Entity<Company>().HasOne(e => e.KeyContact).WithMany().HasForeignKey(e => e.KeyContactId);
Hence a Contact can be the KeyContact of more than one Company.
In order to ensure that a Contact can be the KeyContact of at most one Company the Company.KeyContact relation could be configured by the following line within the OnModelCreating method:
modelBuilder.Entity<Company>().HasOne(e => e.KeyContact).WithOne().HasForeignKey<Company>(e => e.KeyContactId);
But note: This will not ensure that the KeyContact is a member of the Contacts.

Entity Framework core many to many not inserting

I am using EF7 and have a scenario which needs a many to many relationship.
I have a ParticipantSIR entity and a ParticipantAssessmentReport entity. There is a link table ParticipantSIRAssessmentReport between them.
public class ParticipantSIR
{
public int ParticipantSIRID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
public virtual Participant Participant { get; set; }
}
public class ParticipantAssessmentReport
{
public int ParticipantAssessmentReportID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
}
public partial class ParticipantSIRAssessmentReport
{
public int ParticipantSIRID { get; set; }
public int ParticipantAssessmentReportID { get; set; }
public virtual ParticipantAssessmentReport ParticipantAssessmentReport { get; set; }
public virtual ParticipantSIR ParticipantSIR { get; set; }
}
modelBuilder.Entity<ParticipantSIRAssessmentReport>(entity =>
{
entity.HasKey(e => new { e.ParticipantSIRID, e.ParticipantAssessmentReportID });
entity.HasOne(d => d.ParticipantAssessmentReport).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantAssessmentReportID).OnDelete(DeleteBehavior.Restrict);
entity.HasOne(d => d.ParticipantSIR).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantSIRID).OnDelete(DeleteBehavior.Restrict);
});
This appears to be the way this needs to be setup with EF core including the third entity. I got some of the information from. http://ef.readthedocs.io/en/latest/modeling/relationships.html#many-to-many
When I insert data the 2 outside entities get populated but not the link table.
Since there are no navigation properties between the ParticipantSIR and ParticipantAssessmentReport then I'm not sure how to add the linked data.
_db.ParticipantAssessmentReport.Add(participantAssessmentReport);
foreach (var sir in participantSirs)
{
_db.ParticipantSIR.Add(sir);
}
_db.SaveChanges();
Assuming we're talking about EF Core 1.0rc1 it looks like you have created your model correctly (except the virtual keyword doesn't do anything yet as lazy loading hasn't been implemented).
As many-to-many hasn't been implemented yet as of 1.0rc1 you need to do some extra work. See https://github.com/aspnet/EntityFramework/issues/1368#issuecomment-180066124 for the classic blog Post, Tag, PostTag example code.
In your case you need to explictly add to ParticipantSIRAssessmentReport, something like this:
var participantSIRAssessmentReport = new ParticipantSIRAssessmentReport {ParticipantSIR = participantSIR, ParticipantAssessmentReport = participantAssessmentReport };
_db.ParticipantSIRAssessmentReport.Add(participantSIRAssessmentReport);
_db.SaveChanges();
To map Many-To-Many relationships in EF you need to add the following to your DbContext's OnModelCreating() method:
modelBuilder.Entity<ParticipantSIR>()
.HasMany(e => e.ParticipantSIRAssessmentReport)
.WithMany(e => e.ParticipantSIR)
.Map(e => e.ToTable("ParticipantSIRAssessmentReport") //Name of the linking table
.MapLeftKey("ParticipantSIRId") //Name of the Left column
.MapRightKey("ParticipantSIRAssessmentReportId")); //Name of the right column
From here the relationship will be handled using the Collections inside each of the classes.

Entity Framework TPH Inheritance Data Modeling Issues

I'm new to Entity Framework and C#/.Net and trying to create a TPH inheritance model, I'm not sure if I should be or not, so if not, please advise,
Here's the model:
public abstract class Vote
{
public int VoteID { get; set; }
public int UserID { get; set; }
public bool Value { get; set; }
public DateTime DateCreated { get; set; }
public User User { get; set; }
}
public class ProjectVote_ : Vote
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
public class CommentVote_ : Vote //There are three more like this, votes for different hings
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
Now the Project model (comment and model is similar)
public class Project
{
public int ProjectID { get; set; }
public string Title { get; set; }
public virtual ICollection<Vote> Vote { get; set; }
}
What happens is that ICollection creates a database column Project_ProjectID as the foreign key in the Vote table (I think) instead of using the ProjectID I defined. How do I fix it or should I model it differently. If the fluent API is the way to fix it, I don't know how to do that.
In the end I want to be able to use one table to store 5 different types of votes.
When you have related entities you don't need to have a property to store the FK in your model. Entity framework knows that it needs to make a FK to the Project table in ProjectVote when it detects Project in your ProjectVote_ model. Same thing with User and UserId and Comment and CommentId. You don't need to have a property that stores the FK in your model.
You are getting the FK column with the name you don't like "Project_ProjectID" because Entity framework is detecting that it needs to create a FK for your navigation property "Project". It's using it's own naming convention to create the column hence "Project_ProjectID".
If you want to provide your own name for the column override OnModelCreating in your DBContext class and add this fluent mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasMany(p => p.Vote)
.HasRequired(v => v.Project) //or .WithOptional(v => v.Project)
.Map(m => m.MapKey("ProjectId")); //or any other name you want.
}
And for the future this is a helpful reference for how to use the Fluent API. For example here is some documentation on how to custimize TPH with fluent.
Hope that helps!

EF 4.1 Code First ModelBuilder HasForeignKey for One to One Relationships

Very simply I am using Entity Framework 4.1 code first and I would like to replace my [ForeignKey(..)] attributes with fluent calls on modelBuilder instead. Something similar to WithRequired(..) and HasForeignKey(..) below which tie an explicit foreign key property (CreatedBySessionId) together with the associated navigation property (CreatedBySession). But I would like to do this for a one to one relationsip instead of a one to many:
modelBuilder.Entity<..>().HasMany(..).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId)
A more concrete example is below. This works quite happily with the [ForeignKey(..)] attribute but I'd like to do away with it and configure it purely on modelbuilder.
public class VendorApplication
{
public int VendorApplicationId { get; set; }
public int CreatedBySessionId { get; set; }
public virtual Session CreatedBySession { get; set; }
}
public class Session
{
public int SessionId { get; set; }
[ForeignKey("CurrentApplication")]
public int? CurrentApplicationId { get; set; }
public virtual VendorApplication CurrentApplication { get; set; }
public virtual ICollection<VendorApplication> Applications { get; set; }
}
public class MyDataContext: DbContext
{
public IDbSet<VendorApplication> Applications { get; set; }
public IDbSet<Session> Sessions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Session>().HasMany(x => x.Applications).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId).WillCascadeOnDelete(false);
// Note: We have to turn off Cascade delete on Session <-> VendorApplication relationship so that SQL doesn't complain about cyclic cascading deletes
}
}
Here a Session can be responsible for creating many VendorApplications (Session.Applications), but a Session is working on at most one VendorApplication at a time (Session.CurrentApplication). I would like to tie the CurrentApplicationId property with the CurrentApplication navigation property in modelBuilder instead of via the [ForeignKey(..)] attribute.
Things I've Tried
When you remove the [ForeignKey(..)] attribute the CurrentApplication property generates a CurrentApplication_VendorApplicationId column in the database which is not tied to the CurrentApplicationId column.
I've tried explicitly mapping the relationship using the CurrentApplicationId column name as below, but obviously this generates an error because the database column name "CurrentApplicationId" is already being used by the property Session.CurrentApplicationId:
modelBuilder.Entity<Session>().HasOptional(x => x.CurrentApplication).WithOptionalDependent().Map(config => config.MapKey("CurrentApplicationId"));
It feels like I'm missing something very obvious here since all I want to do is perform the same operation that [ForeignKey(..)] does but within the model builder. Or is it a case that this is bad practise and was explicitly left out?
You need to map the relationship as one-to-many and omit the collection property in the relationship.
modelBuilder.Entity<Session>()
.HasOptional(x => x.CurrentApplication)
.WithMany()
.HasForeignKey(x => x.CurrentApplicationId)