How to handle self references in Entity Framework code-first? - c#

These are my models (simplified):
public User()
{
Friends = new HashSet<User>();
Subscriptions = new HashSet<Subscription>();
Tasks = new HashSet<Task>();
Invitations = new HashSet<Invitation>();
Events = new HashSet<Event>();
}
public Guid UserId { get; set; }
public DateTime MemberSince { get; set; }
[StringLength(450)]
[Index("UserNameIndex", IsUnique = true)]
public string NickName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAdress { get; set; }
public string HashedPassword { get; set; }
public virtual ProfilePicture ProfilePicture { get; set; }
public bool Validated { get; set; }
ICollection<Event> Events { get; set; }
ICollection<User> Friends { get; set; }
And the Event model:
public class Event
{
public string EventName { get; set; }
public Guid EventId { get; set; }
public Guid UserId { get; set; }
public DateTime? Time { get; set; }
public string Location { get; set; }
public DateTime? EventDate { get; set; }
public virtual User User { get; set; }
public ICollection<User> Participants { get; internal set; }
}
Here's the model creation:
modelBuilder.Entity<User>().HasKey(u => u.UserId);
modelBuilder.Entity<User>().
HasMany<User>(u => u.Friends).
WithMany();
modelBuilder.Entity<User>().
HasMany<Event>(u => u.Events).
WithMany();
Now the problem is the following: my tables look like this:
It seems like the relations are not the way they should be...
User table:
Event table:
Automatically created UserEvents:
Now what I expected is when creating a new Event (UserId) is required there. I get a new Entry in the Events table + a new one in the UserEvents....
What am I missing here ?

You have two different relations between User and Event. A one-to-many relation and a many-to-many relation.
The first is a one-to-many relationship between the event and the creator of the event (The user and userid properties on Event)
When you add a new Event with the required UserId, there will not be a record created in the automatically created UserEvents table, because you have a one-to-many relationship here. So simply creating an Event with the userid will not lead to a record in the UserEvents table.
The second is the many-to-many relationship between Event and it's participants. When you add an event with participants. There would be records also inserted into the UserEvents table. Only participants will appear in the UserEvents table. You should however create your many-to-many mapping with a reference to your property Participants in the Event class to make this possible.
modelBuilder.Entity<User>().HasMany<Event>(u => u.Events).WithMany(m => m.Participants);

Related

Navigation Property in EF Core is NULL

Can someone guide me on how to construct the proper property navigation on the below tables?
I have these tables in my database:
I then need to relate the Status table to get the status Name on every table
These are my model classes:
[Table("Companies")]
public class CompanyEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Status_Id { get; set; }
public DateTime Created_Date { get; set; }
public DateTime Updated_Date { get; set; }
[ForeignKey("Status_Id")]
public Status Status { get; set; }
}
[Table("Customers")]
public class CustomerEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Status_Id { get; set; }
}
[Table("Status")]
public class Status
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
}
My goal is to when I get all the Companies or Customers using DBContext e.g. var companies = _dbContext.Companies.ToList(). I want to get the status of every record for Companies and Customers. I'm not so sure how to construct the navigation property properly for the said models.
TIA!
Update #1
After following the below suggestion, yes the Status is not NULL anymore. But, it gets the wrong status id. Instead of using the Status_Id, it uses the Id of the Company. Please see below snippets. The status of the said company is 6.
But if you notice on the 2nd snip, the status is 3 which is the Id of the Company.
I also have this code in OnModelCreating.
modelBuilder.Entity<Company>()
.HasOne<Status>()
.WithMany()
.HasForeignKey(p => p.Status_Id);
This is the reason why I get that behavior. But if I removed this, the Status property is gets NULL.
Update #2
Just fixed my issue. I need to change the Company property from public Company Company { get;set; } to public List Companies { get;set; }. Then add the suggested answer Include`.
[Table("Status")]
public class Status
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Company> Companies { get; set; }
}
You have to add the .Include() method to join your status table in your result like so:
_dbContext.Companies.Include(c => c.Status).ToList()

The entity type 'AlarmReciever' requires a primary key to be defined

I have two models that both have a many-to-one relation and a many-to-many relation.
A user can create many alarms but an alarm can only have one creator.
A user can recieve many alarms and alarms have many recievers.
public class AppUser
{
public Guid Id { get; set; }
public virtual IEnumerable<Alarm> CreatedAlarms { get; set; }
[InverseProperty("AlarmRecievers")]
public virtual IEnumerable<Alarm> RecievedAlarms { get; set; }
public AppUser()
{
CreatedAlarms = new HashSet<Alarm>();
RecievedAlarms = new HashSet<Alarm>();
}
}
and
public class Alarm
{
public Guid Id { get; set; }
[ForeignKey("AppUser")]
public Guid? AppUserId { get; set; }
public AppUser AppUser { get; set; }
[InverseProperty("RecievedAlarms")]
public virtual IEnumerable<AppUser> AlarmRecievers { get; set; }
public Alarm()
{
AlarmRecievers = new HashSet<AppUser>();
}
}
but when I try to add a migrations, I get the title as an error. I did expect there to be a created a AlarmReciever table but considering I don't have the model in my code I don't know how to create the key.
Earlier I had
modelBuilder.Entity<AlarmReciever>()
.HasKey(c => new { c.AppUserId, c.AlarmId });
but I'm trying to get rid of the join table in my code so I'd like to do it the way I'm trying to

Entity Framework: The Entity with schema was already defined

In my database, I have two tables, namely Book and User, that have a M-to-M relationship table named BookXUser. In the relationship table, there are two fields, which are the foreign keys of the Book and User, and another field named bookShelf. Since using only Entity Framework I couldn't find a way to insert a M-to-M relationship while inserting into the bookShelf at the same time, I decided to make a model for the relationship table, which are defined like this:
modelBuilder.Entity<Book>()
.HasMany<UserData>(s => s.user)
.WithMany(c => c.book)
.Map(cs =>
{
cs.MapLeftKey("bookID");
cs.MapRightKey("userID");
cs.ToTable("BookXUser");
});
modelBuilder.Entity<BookXUser>()
.HasRequired<UserData>(s => s.user)
.WithMany(g => g.bookXuser)
.HasForeignKey<int>(s => s.userID);
modelBuilder.Entity<BookXUser>()
.HasRequired<Book>(s => s.book)
.WithMany(g => g.bookXuser)
.HasForeignKey<int>(s => s.bookID);
The problems arrive when I execute the following line:
userDataRepo.FindBy(user => user.email == entry.email).FirstOrDefault()
The EntitySet 'BookUserData' with schema 'dbo' and table 'BookXUser' was already defined. Each EntitySet must refer to a unique schema and table.
How can I solve this error while keeping the relationship table as a model which references the BookXUser? The idea is that I am using lazy loading on the virtual methods found in the model of BookXUser so I can get my books and users more easily, and I also manually insert my data in it so I can populate the bookshelf field.
public class BookXUser : IEntityBase
{
public int ID { get; set; }
public int bookID { get; set; }
public virtual Book book { get; set; }
public int userID { get; set; }
public virtual UserData user { get; set; }
public string bookShelf { get; set; }
}
The Book entity
public class Book : IEntityBase
{
public int ID { get; set; }
public string title { get; set; }
public string isbn { get; set; }
public int noPage { get; set; }
public string edition { get; set; }
public string bLanguage { get; set; }
public byte[] bookPic { get; set; }
public string publisherSite { get; set; }
public string bookFormat { get; set; }
public DateTime releaseDate { get; set; }
public DateTime initialReleaseDate { get; set; }
public string publisher { get; set; }
public string overview { get; set; }
public virtual ICollection<Author> author { get; set; }
public virtual ICollection<Genre> genre { get; set; }
public virtual ICollection<UserData> user { get; set; }
public virtual ICollection<Rating> rating { get; set; }
public virtual ICollection<Review> review { get; set; }
public virtual ICollection<BookXUser> bookXuser { get; set; }
public Book()
{
this.author = new HashSet<Author>();
this.genre = new HashSet<Genre>();
this.user = new HashSet<UserData>();
this.rating = new HashSet<Rating>();
this.review = new HashSet<Review>();
this.bookXuser = new HashSet<BookXUser>();
}
}
The UserData enity
public class UserData : IEntityBase
{
public int ID { get; set; }
public string username { get; set; }
public string userpass { get; set; }
public string email { get; set; }
public byte[] userPic { get; set; }
public string userOverview { get; set; }
public DateTime joinedDate { get; set; }
public virtual ICollection<Book> book { get; set; }
public virtual ICollection<Review> review { get; set; }
public virtual ICollection<Rating> rating { get; set; }
public virtual ICollection<UserData> user { get; set; }
public virtual ICollection<BookXUser> bookXuser { get; set; }
public UserData()
{
this.book = new HashSet<Book>();
this.review = new HashSet<Review>();
this.rating = new HashSet<Rating>();
this.user = new HashSet<UserData>();
this.bookXuser = new HashSet<BookXUser>();
}
}
You defined your Book and UserData entities in a wrong way. As you say in your question you are trying to define a many-to-many relashionship between Book and UserData so you added a join entity BookXUser because that entity hold a data BookShelf. That join entity will use by convention BookXUser as a table name.
Also, you defined two collections:
ICollection<UserData> user in your Book entity
ICollection<Book> book in your UserData entity
You also use this fluent configuration in your OnModelCreating method:
modelBuilder.Entity<Book>()
.HasMany<UserData>(s => s.user)
.WithMany(c => c.book)
.Map(cs =>
{
cs.MapLeftKey("bookID");
cs.MapRightKey("userID");
cs.ToTable("BookXUser"); // <-- this is your error.
});
Doing it like this, you're adding another many-to-many relashionship between Book and UserData and use BookXUser as the name for the join table which is already used by your entity BookXUser you've created.
To solve your issue you don't need to add the collections and to configure many-to-many relationship fluently like you did. Why? Bacause you have an join entity BookXUser.
So :
remove ICollection<UserData> user from your Book entity and replace it with ICollection<BookXUser> BookXUser
remove ICollection<Book> book from your UserData entity and replace it with ICollection<BookXUser> BookXUser
remove the fluent configuration you added for many-to-many relashionship between UserData and Book.

Adding Extra Column to join table

I currently have an employee model
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<StateLicenseType> Licenses { get; set; }
and a License Type Model
public class StateLicenseType
{
public int StateLicenseTypeId { get; set; }
public string State { get; set; }
public string LicenseName { get; set; }
public virtual Employee Employee { get; set; }
}
This relationship can be one to many, but I also need to add some information to the license when saved. I need to be able to store the employees unique license number and have not been able to find out how to do this while searching around. Is there a way to have Entity Framework add a column to a join table and then even if I have to, update it myself?
Is there a better/different way to model this relationship with EF?
In an old DB the table was created like this,
CREATE TABLE `nmlsstatelicenses` ( `peopleid` int(11) DEFAULT NULL, `statelicensetypeid` int(11) DEFAULT NULL, `licensenumber` varchar(25) DEFAULT NULL)
You need to create a third entity which will be a linking entity (like a linking table in many-to-many relationships in database. Here is an example: many-to-many relationships with additional information.
So you would have the following entities in your model:
public Employee
{
public string EmployeeId { get;set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<LicenseRegistration> RegisteredLicenses { get; set; }
}
public LicenseType
{
public int StateLicenseTypeId { get; set; }
public string State { get; set; }
public string LicenseName { get; set; }
public virtual ICollection<LicenseRegistration> RegisteredLicenses { get; set; }
}
public LicenseRegistration
{
//properties for the additional information go here
/////////////////////////////////////////////////////
public int EmployeeId {get;set;}
[ForeignKey("EmployeeId")]
public Employee Employee {get;set;}
public int LicenseTypeId {get;set;}
[ForeignKey("LicenseTypeId")]
public LicenseType {get;set;}
}
Then, in your DBContext file, you will need to define 1-to-many relationship between Employee and LicenseRegistration, and between LicenseType and LicenseRegistration.
Hope this helps!
UPDATE
Here is how you would set up the relationships:
modelbuilder.Entity<LicenseRegistration>()
.HasRequired(lr => lr.LicenseType)
.WithMany(lt => lt.RegisteredLicenses)
.HasForeignKey(lr => lr.LicenseTypeId);
modelbuilder.Entity<LicenseRegistration>()
.HasRequired(lr => lr.Employee)
.WithMany(e => e.RegisteredLicenses)
.HasForeignKey(lr => lr.EmployeeId);

Are my tables linked correctly for Entity Framework?

Forewarning: I know approximately nothing when it comes to MVC/Entity Framework/Linq queries.
I'm having an issue when I try to query the database. Here's the query I'm using:
int? UserId = db.StudentModel
.Where(c => c.UserName == certUserName)
.Select(c => c.UserId)
.FirstOrDefault();
When it searches the database, it successfully retrieves the UserId.
The problem is that I then use the following query:
CompletionsModel student = db.Completions.Find(UserId);
When I do this, it throws an inner exception that states
{"Invalid column name 'UserProfile_UserId'."}
The weird thing is that when I go to my code and mouse over the 'db' part of the command to see what data it's holding, it has CourseModel, StudentModel, and Completions (though the model's actual filename is CompletionsModel - is that a clue?), so it seems like they're linked properly.
Here's the code for my three models and the database context.
CompletionsModel (UserProfile is white text in my code; not sure why it's teal here Same with UserId and CompletionDate):
[Table("Completion")]
public class CompletionsModel
{
[Key]
public int UserId { get; set; }
public string PRD_NUM { get; set; }
public DateTime CompletionDate { get; set; }
public virtual CourseModel PRD { get; set; }
public virtual StudentModel UserProfile { get; set; }
}
CourseModel:
[Table("PRD")]
public class CourseModel
{
[Key]
public string PRD_NUM { get; set; }
public string PRD_TIT { get; set; }
//because any number of students can be enrolled in one course
public virtual ICollection<CompletionsModel> CompletionsModel { get; set; }
}
StudentModel:
[Table("UserProfile")]
public class StudentModel
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<CompletionsModel> CompletionsModel { get; set; }
}
DBContext:
public class ClassContext : DbContext
{
public ClassContext()
: base("DefaultConnection")
{
}
public DbSet<StudentModel> StudentModel { get; set; }
public DbSet<CompletionsModel> Completions { get; set; }
public DbSet<CourseModel> CourseModel { get; set; }
}
And finally, an image of my database layout - maybe this will help things along, too:
I'm too at the beginning of Entity Framework, but what does irritate me is, that you have kind of foreign key relationship between Completion and UserProfile, without really defining, a int column as foreign key in your Completion Table.
You could try to change
public virtual StudentModel UserProfile { get; set; }
to something like
public virtual int StudentId { get; set; }
[ForeignKey("StudentId")]
public virtual StudentModel UserProfile { get; set; }
The same for PRD
But maybe a change in your database is not what you want.
What you could also do, is to remove these lines
public virtual CourseModel PRD { get; set; }
public virtual StudentModel UserProfile { get; set; }
Hope that helps.
EDIT:
I guess the problem is, that you are missing the ForeignKey Attribute at your UserProfile property in your Completions table.
So use
[ForeignKey("UserId")]
public virtual StudentModel UserProfile { get; set; }
instead of
public virtual StudentModel UserProfile { get; set; }
if the UserIds are representing the same user

Categories

Resources