I have an entity User. Each User is supposed to have many Friends and Teachers. With EF Code First I am a little bit confused on how to achieve what I want. I saw examples of self reference, but not many-to-many. For example:
public class Employee
{
#region Properties
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? ManagerID { get; set; }
public Employee Manager { get; set; }
#endregion
}
and the modelBuilder:
modelBuilder.Entity<Employee>().
HasOptional(e => e.Manager).
WithMany().
HasForeignKey(m => m.ManagerID);
How to create an entity with self-reference in my case, where there are Friends (if a is friend with b this means that b is friend with a) and Teachers (if a is teacher of b, b is student of a)?
Sorry if there already exists a similar thread.
Any help is greatly appreciated.
Ok, so because a lot of people answered this ;), I searched a little more and I found the solution for my case. The entity looks like:
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
public virtual ICollection<User> Friends { get; set; }
}
The modelBuilder:
modelBuilder.Entity<User>()
.HasMany(x => x.Friends)
.WithMany();
And finally I made helper methods BeFriend and BecomeTeacherStudent, which make sure that when you become friend with someone he becomes friend with you (and the respective thing for teacher-student).
Related
My current aim is to build a database structure using classes in Entity Framework & ASP MVC.
I currently have a Users table and a Posts table. What I would like to do is create a many to many relationship for Users who have liked Posts (whilst conserving who created the post). And be able to access for each user all of the posts they have liked. Currently I have these classes but I'm unsure of how to link them as all of the online examples are linking Primary Keys from different databases where I just want to use the Username Parameter. Any help would be great. I have this so far.
public class Posts
{
[Key]
public virtual int PostId { get; set; }
public virtual string Title { get; set; }
public virtual string URL { get; set; }
[DisplayName("Main Text")]
public virtual string TextBody { get; set; }
public int PostLikes { get; set; }
private DateTime Datedata = DateTime.Now;
public DateTime PostDate { get { return Datedata; } set { Datedata = value; } }
public virtual Users User { get; set; }
public ICollection<PostLikes> UsersWhoHaveSigned { get; set; }
}
{
public class Users
{
[Key]
public virtual int UserId { get; set; }
public virtual string Username { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual List<Posts> Post { get; set; }
}
}
I have not built the UsersWhoHaveSigned table yet. Early experimentation caused me so much backtracing it was painful. Any help would be great.
Edit: I was hoping to ask for help and then appropriate that informtaion to fit my example which utilises the individual accounts add-on. This produces some addition files that are now causing interference with the code you've provided.
Here is the IdentityModels.cs file.
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Coursework2.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
I believe that the assembly directives at the top are preventing system.data.entity from being used so when I try to implement ApplicationDbContext : DbContext I get error messages :/
Ideally I'm looking to use the IdentityModels.cs file as a replacement for the users class. But still very lost.
First of all, I recommend that you use the singular form for your class names, as EF will automatically pluralize table names.
Second, for a key property, you can just use the term Id, without any annotations, and EF will pick it up as the principal key.
Finally, I'll assume you are looking to use a Code-First approach. Consider the following classes (yours, but refactored for clarity purpose):
public class Post
{
public virtual Guid Id { get; set; }
public virtual string UserName { get; set; }
public virtual User User { get; set; }
public virtual ICollection<PostLike> Likes { get; set; }
}
public class PostLike
{
public virtual Guid Id { get; set; }
public virtual Guid PostId { get; set; }
public virtual Post Post { get; set; }
public virtual string UserName { get; set; }
public virtual User User { get; set; }
}
public class User
{
public virtual Guid Id { get; set; }
public virtual string UserName { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<PostLike> Likes { get; set; }
}
To make it work, you'd need a DbContext such as the following. Pay attention to the OnModelCreating method, which is where the magic happens:
public class ApplicationDbContext
: DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<PostLike> PostLikes { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasAlternateKey(u => u.UserName);
modelBuilder.Entity<User>()
.HasMany(u => u.Posts)
.WithOne(p => p.User);
modelBuilder.Entity<Post>()
.HasOne(p => p.User)
.WithMany(u => u.Posts)
.HasForeignKey(p => p.UserName)
.HasPrincipalKey(u => u.UserName);
modelBuilder.Entity<Post>()
.HasMany(p => p.Likes)
.WithOne(pl => pl.Post);
modelBuilder.Entity<PostLike>()
.HasOne(pl => pl.Post)
.WithMany(p => p.Likes);
modelBuilder.Entity<PostLike>()
.HasOne(pl => pl.User)
.WithMany(u => u.Likes)
.HasForeignKey(pl => pl.UserName)
.HasPrincipalKey(u => u.UserName);
}
}
Voila! I hope it answers your question ;)
If so, please don't forget to mark my post as the answer!
Edit:
I'll provide some explanations, that I had left out to answer your question ASAP.
So, first thing you need to do, is to declare the UserName as an alternate key, because you want to create relationships depending on it, and you already have the 'Id' principal key declared.
Second, on each object that should own a User reference base on the UserName alternate key, you need to declare the object's UserName property as the foreign key of the relationship, and the User's UserName property as the principal key.
In other words, the foreign key is the property that a referencing object uses for the relationship, and the principal key is the property based on which the referenced object is bound to the referencing one.
Note that principal keys must have a key or alternate key constraint, or it won't work.
Just to comment on your answer. I found that I had to use
using Microsoft.EntityFrameworkCore and remove System.Data.Entity - This was causing the program to be confused as to which DbContext I wanted to use. Thanks!
public class Club
{
public int ClubId { get; set; }
public string Name { get; set; }
public ICollection<Membership> Memberships { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public ICollection<Membership> Memberships { get; set; }
}
public class Membership
{
public int ClubId { get; set; }
public int PersonId { get; set; }
public Club Club { get; set; }
public Person Person { get; set; }
}`
...
modelBuilder.Entity<Person>()
.HasMany(p => p.Memberships)
.WithRequired(m => m.Person)
.WillCascadeOnDelete(true);
Above is a simple set up of two independent entities: Club and Person, which may be joined via membership.
I had thought that I could simply retrieve a Person, delete it, and then rely on cascade deleting to remove any Membership records and leave the associated Clubs in place (they exist independently).
Similarly, I had thought that I could simply retrieve a Club, delete it, and then rely on cascade deleting to remove amy Membership records and leave the associated Persons in place (they exist independently).
It seems that when I remove a "Person", EF tries to set the value of Membership.PersonId to null causing a db violation.
Have I misunderstood cascade deleting? And how could I achieve the desired result?
I guess that I could retrieve Person.Include("Membership") and then enumerate each membership and mark as deleted and then finally mark the Person as deleted ... but is this really required or am I missing a trick?
Having some issues with relationships within EntityFramework 6.
I know that DataAnnotations or FluentApi can be used and I'm okay with using either.
Here's an example of relationship I'd like to accomplish:
Student has one ImmunizationRecord
ImmunizationRecord has Multiple ShotRecords
This seems like it would be fairly straight forward, however it doesn't seem to be working as expected.
Here's example code (Updated from actual code)
public class Student : Entity
{
[Key]
public int Id { get; set; }
public String Name { get; set; }
//...
[ForeignKey(nameof(Id))]
public virtual ImmunizationRecord ImmunizationRecord { get; set; }
}
public class ImmunizationRecord : Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Key]
[ForeignKey(nameof(Student))]
public int StudentId { get; set; }
public String Name { get; set; }
public DateTime LastUpdated { get; set; }
public virtual Student Student { get; set; }
public virtual ICollection<ShotRecord> ShotRecords { get; set; }
}
public class ShotRecord: Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
// Want this to point back to ImmunizationRecord
public int ImmunizationRecordId { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public DateTime DateOfShot { get; set; }
//...
[ForeignKey("ImmunizationRecordId")]
public virtual ImmunizationRecord ImmunizationRecord { get; set; }
}
Example fluentapi might be something like this:
modelBuilder.Entity<Student>().HasOptional(c => c.ImmunizationRecord).WithRequired(m => m.Student);
modelBuilder.Entity<Student>().HasOptional(c => c.ImmunizationRecord).WithRequired(sc => sc.Student);
modelBuilder.Entity<ImmunizationRecord>().HasMany(sc => sc.ShotRecords).WithRequired(sr => sr.ImmunizationRecord);
The Result
I suspect that I'm just missing a small piece of what needs to be done, or missing the proper way to configure these entities with a similar relationship.
With the code above and class structure, I can create a Student, and Create a ImmunizationRecord, and ShotRecords without issue.
The issue occurs when I try to retrieve the ShotRecords from The ImmunizationRecord, EntityFramework will resolve on the key on the Student.Id instead of using the key of on the ImmunizationRecord.Id.
I can go into the database and change the rows for ShotRecords and update the ImmunizationRecordId to the StudentId and they'll resolve properly. But as stated before, I want them to use the key of the ImmunizationRecord, and not the student.
Any advice and guidance would be greatly appreciated!
Thank you in advance!
(Updated to a different example to make more sense)
I am fairly new to using Code First approach with entity framework and I know that I you have a many to many relationship like the entities below, the EF will create the intermediary table automatically:
class Post {
...
public virtual ICollection<Category> Categories {get; set;}
...
}
class Category {
...
public virtual ICollection<Post> Posts {get; set;}
...
}
However, if in the intermediary table I need to have extra data fields, one possible way (which I currently like, maybe because I am unaware of better ways) would be defining a new Entity of my own, like:
class Posts_Categories {
public int Id {get; set;}
public int CategoryId {get; set;}
public int PostId {get; set;}
public string Exrtafield1 {get; set;}
public int ex extraField2 {get; set;}
...
public virtual Post Post {get; set;}
public virtual Category Category {get; set;}
}
Using this approach, EF does create my custom intermediary table, but it also creates another one of its own called "PostsCategories" which only contains a foreign key to Post_Id and another to Category_Id.
How do I make it not create that extra one and use the one I have defined?
Is this a good way to manage Many to Many relationships with extra data fields??
you should use one to many relation like this :
public class Post
{
public System.Int32 PostId { get; set; }
[InverseProperty("Post")]
public virtual ICollection<Posts_Category> PostCategories { get; set; }
}
public class Category
{
public System.Int32 CategoryId { get; set; }
[InverseProperty("Category")]
public virtual ICollection<Posts_Category> PostCategories { get; set; }
}
public class Posts_Category
{
public System.Int32 PostId { get; set; }
public System.Int32 CategoryId { get; set; }
[ForeignKey("PostId")]
[InverseProperty("PostCategories")]
public virtual Post Post { get; set; }
[ForeignKey("CategoryId")]
[InverseProperty("PostCategories")]
public virtual Category Category { get; set; }
}
I needed to expand a bit on Iraj's answer to make it work. Another modification is that I'm including the default ApplicationUser as one of my tables.
So the relation is ApplicationUser 1-∞ IdeaVote ∞-1 Idea (i.e. there are users and ideas, users can vote on ideas, and each vote is represented with a connection between an ApplicationUser and an Idea.
public class Idea
{
[Key]
public int Id { get; set; }
// This is an ordinary data field
public string Text { get; set; }
[InverseProperty("Idea")]
public virtual ICollection<IdeaVote> Votes { get; set; }
}
public class IdeaVote
{
[Key]
public int Id { get; set; }
public int IdeaId { get; set; }
[ForeignKey("IdeaId")]
[InverseProperty("Votes")]
public virtual Idea Idea { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
[InverseProperty("Votes")]
public virtual ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser
{
[InverseProperty("User")]
public virtual ICollection<IdeaVote> Votes { get; set; }
// Default stuff
}
It is normal for it to create that PostsCategories table for the relation between the two, and you are going to want that. If c is a Category, you'll be able to do things like c.Posts
Normally you would not create your own table manually for that. What kinds of data would you be keeping in the "extra" fields? I would probably move the fields to one of the other tables and drop that one. Most many to many relationship tables do not contain extra fields.
I have the following scenario in my project and I'm starting to get with a few questions about it.
On the User class, querying QuestionFollows directly will be better (in terms of performance) than querying Questions and then QuestionFollows?
Another question ... is the relationship between User and QuestionFollow necessary/redudant?
Domain
public class User
{
public long UserID { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<QuestionFollow> QuestionFollows { get; set; }
}
public class Question
{
public long QuestionID { get; set; }
public long UserID { get; set; }
public string Description { get; set; }
public User User { get; set; }
public virtual ICollection<QuestionFollow> QuestionFollows { get; set; }
}
public class QuestionFollow
{
public long QuestionFollowID { get; set; }
public long QuestionID { get; set; }
public long UserID { get; set; }
public DateTime Date { get; set; }
public Question Question { get; set; }
public User User { get; set; }
}
Everything you're doing seems right based on assumptions I gather from your code: a user has questions and can also follow questions that belong to other users. If that's correct, then it seems you're simply struggling with how these relationships play out.
From User, querying Questions will return all questions belonging to that User instance. Querying QuestionFollows, however, will return all questions that User has chosen to follow, whether or not those questions belong to that User instance. In other words, these are two functionally different datasets, so it's not about which is more performant; it's about which returns the data you actually need.
The relationship between User and QuestionFollows is also not redundant, because again you're tracking a fundamentally different facet of the relationship. In QuestionFollow the User is the entity that is following the question and the Question is the entity that is being followed. That Question entity could have a completely different User attached to it, as that User entity is the one who owns the question.