I am trying to build a data model that I can use with Entity Framework 4.1.
I am tring to build a simple app to manage events (like a birthday party). So I figure I will have two types of users, Admins and Attenders. The admins will be able to create and manage the event and the attenders will only be able to view an event they are invited to.
I thought I only needed 2 classes for this but I am not sure. Here is waht I did for EF4.1
public class user
{
public int id { get; set; }
public string name { get; set; }
public ICollection<myevent> myadminevents { get; set; }
public ICollection<myevent> myinvites { get; set; }
}
public class myevent {
public int id { get; set; }
public string name { get; set; }
public ICollection<user> admin { get; set; }
public ICollection<user> attend { get; set; }
}
public class myeventcontext : DbContext
{
public DbSet<user> users { get; set; }
public DbSet<myevent> events { get; set; }
}
EF didnt do what I thought it would. It is ignoring my collections. So I don't think the model is right.
Any suggestions?
You can model this using two junction tables and configure the many-to-many relationships using fluent API
public class myeventcontext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Entity<User>()
.HasMany(user => user.myinvites).WithMany(event => event.attend)
.Map(m =>
{
m.ToTable("EventAttendees");
m.MapLeftKey("UserId");
m.MapRightKey("EventId");
});
Entity<User>()
.HasMany(user => user.myadminevents).WithMany(event => event.admin)
.Map(m =>
{
m.ToTable("EventAdmins");
m.MapLeftKey("UserId");
m.MapRightKey("EventId");
});
}
}
Other approach would be to have a single junction table with an additional column to store whether attendee is is an admin or not. But in this way you will not be able model many-to-many relationship without including the junction table as a class.
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!
I'm trying to wrap my head around a Many-to-Many relationship with Code-First mapping.
If I have an Album Class that can have many Genres (and vice-versa), I understand that I need to have an Intermediate table and Entity Framework will automatically do that for me. However, I would like a little more control over the Intermediate table, so I am creating one myself, the main reason is that I would like to be able to mark the row as deleted from the front-end and leave it in the database.
To do this for all my Classes I have created a BaseObject that they are Inherit from (I've removed many of the Annotations and other code to simplify this post):
public class BaseObject
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Oid { get; set;
public DateTime DateCreated { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletedDate { get; set; }
}
After that we have the Albums and Genres Classes:
public class Album : BaseObject
{
public string Name { get; set; }
public virtual List<AlbumsGenres> Albums { get; set; }
}
public class Genre : BaseObject
{
public string Name { get; set; }
public virtual List<AlbumsGenres> Genres { get; set; }
}
Finally the AlbumsGenres Intermediate Class:
public class AlbumsGenres : BaseObject
{
// Left blank because EF will create "Album_Oid" and "Genre_Oid" columns
// Tried the below code, but EF still created it's own Columns
/*
public Guid Album { get; set; }
public Guid Genre { get; set; }
*/
}
The questions that I have; Is there a way to tell EF to create Album_Oid with a different Column Name like Album?
I would accept an answer of "Just don't worry about it", if a brief explanation (or link) was provided.
You can control the intermediate table, Normally I use explicit mapping but the following works with CodeFirst:
In Album, you want a List<Genre> (not AlbumGenre)
In Genre, you want a List<Album>
In your context, add the following override for OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Album>()
.HasMany(a => a.Genres)
.WithMany(g => g.Albums)
.Map(x =>
{
x.MapLeftKey("AlbumId");
x.MapRightKey("GenreId");
x.ToTable("AlbumGenres");
});
base.OnModelCreating(modelBuilder);
}
I have two entities in my MVC application and I populated the database with Entity Framework 6 Code First approach. There are two city id in the Student entity; one of them for BirthCity, the other for WorkingCity. When I define the foreign keys as above an extra column is created named City_ID in the Student table after migration. Id there a mistake or how to define these FKs? Thanks in advance.
Student:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
City:
public class City
{
public int ID { get; set; }
public string CityName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
To achieve what you want you need to provide some aditional configuration.Code First convention can identify bidirectional relationships, but not when there are
multiple bidirectional relationships between two entities.You can add configuration (using Data Annotations or the Fluent API) to present this
information to the model builder. With Data Annotations, you’ll use an annotation
called InverseProperty. With the Fluent API, you’ll use a combination of the Has/With methods to specify the correct ends of these relationships.
Using Data Annotations could be like this:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("Students")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
This way you specifying explicitly that you want to relate the BirthCity navigation property with Students navigation property in the other end of the relationship.
Using Fluent Api could be like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.Students).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany().HasForeignKey(m=>m.LivingCityId);
}
With this last solution you don't need to use any attibute.
Now, the suggestion of #ChristPratt in have a collection of Student in your City class for each relationship is really useful. If you do that, then the configurations using Data Annotations could be this way:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("BirthCityStudents")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
[InverseProperty("LivingCityStudents")]
public virtual City LivingCity { get; set; }
}
Or using Fluent Api following the same idea:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.BirthCityStudents).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany(m => m.LivingCityStudents).HasForeignKey(m=>m.LivingCityId);
}
Sheesh. It's been a long day. There's actually a very big, glaring problem with your code, actually, that I completely missed when I commented.
The problem is that you're using a single collection of students on City. What's actually happening here is that EF can't decide which foreign key it should actually map that collection to, so it creates another foreign key specifically to track that relationship. Then, in effect you have no navigation properties for the collections of students derived from BirthCity and LivingCity.
For this, you have to drop down to fluent configuration, as there's no way to configure this properly using just data annotations. You'll also need an additional collection of students so you can track both relationships:
public class City
{
...
public virtual ICollection<Student> BirthCityStudents { get; set; }
public virtual ICollection<Student> LivingCityStudents { get; set; }
}
Then, for Student:
public class Student
{
...
public class StudentMapping : EntityTypeConfiguration<Student>
{
public StudentMapping()
{
HasRequired(m => m.BirthCity).WithMany(m => m.BirthCityStudents);
HasRequired(m => m.LivingCity).WithMany(m => m.LivingCityStudents);
}
}
}
And finally in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Student.StudentMapping());
}
I am not sure how to get the results from joining the tables in controllers.
There're 3 tables 'Groups' 'Users' 'GroupUser' (bridge table).
public class Group
{
[Key]
public int GroupID { get; set; }
public string Group_Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public int UserID { get; set; }
public string User_Name { get; set; }
public ICollection<Group> Groups { get; set; }
}
I also have this EFContext class
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Group>()
.HasMany(g => g.Users)
.WithMany(u => u.Groups)
.Map(m =>
{
m.MapLeftKey("UserID");
m.MapRightKey("GroupID");
m.ToTable("GroupUSer");
});
Do I also need to build a GroupUser class (to represent the GroupUser bridge table)?
Then how do I get the results when joining the 3 tables to get list of groups and users?
GroupViewModel model = new GroupViewModel
{
Groups = .... // this should be a linq statement that get results
that contains all groups and users
};
The equal sql statemen would be
select *
from Group g
join GroupUser gu on g.GroupID=gu.GroupID
join User u on u.UserID=gu.UserID
No, intermediate class is not needed.
The main point of an ORM (Object-Relational Mapper, which is what Entity Framework is) is to abstract away the database and let you work in a pure object-oriented way. Intermediate tables are definitely a database term and are not needed here.
The only reason I can think of that may lead you to create an intermediate class is when you need a "payload" (an extra meta-data) on the association. For example:
public class User
{
public int Id { get; set; }
public int Email { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
public class Account
{
public int Id { get; set; }
public virtual ICollection<User> Users { get; set; }
}
Now, if you want the user-to-account association to define whether the association is of "Own the account" type (Administrator), you can do something like:
public class User
{
public int Id { get; set; }
public int Email { get; set; }
public virtual ICollection<AccountUserAssociation> Accounts { get; set; }
}
public class Account
{
public int Id { get; set; }
public virtual ICollection<AccountUserAssociation> Users { get; set; }
}
public class AccountUserAssociation
{
public virtual User User { get; set; }
public virtual Account Account { get; set; }
public AssociationType AssociationType { get; set; }
}
public enum AssociationType { Regular, Administrator }
I am using Code First appraoch in my MVC project. Just for simplicity I have got two classes Course and Modules where there is a many to many relationship between the entities. Here is the structure of my classes
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<Module> Modules { get; set;
}
public class Module
{
public int ModuleId { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Course> Courses {get;set;
}
I created two controllers for each using EntityFramework where each controller creates the table for each class. Now having the many to many relationship the EF is smart enough to create the third junction table for me ie. CourseModule(FK_Course_ID, FK_ModuleID). But the problem is this table is not get populated with the keys from the respective tables. It is blank. Can anyone please help me how can I figure it out??
Thanks.
I am going out on a limb here but do you have the following in your
context class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>()
.HasMany(s => s.Modules)
.WithMany(a => a.Courses)
.Map(m =>
{
m.MapLeftKey("CourseID");
m.MapRightKey("ModuleID");
m.ToTable("CourseModules");
});
}
Also, can we see your code where you try to save Course / Module?