How to insert properly into a many-to-many relationship? - c#

When inserting data into a many-to-many relationship, should you insert to the join-table or to both original tables?
My table models:
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<GroupMember> GroupMembers { get; set; }
The relationship between them is configured with Fluent API:
builder.Entity<GroupMembers>().HasKey(gm => new { gm.UserId, gm.GroupId });
builder.Entity<GroupMembers>().HasOne(gm => gm.Group).WithMany(group => group.GroupMembers).HasForeignKey(gm => gm.GroupId);
builder.Entity<GroupMembers>().HasOne(gm => gm.User).WithMany(user => user.GroupMembers).HasForeignKey(gm => gm.UserId);
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public List<GroupMember> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public List<GroupMembers> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class GroupMembers
{
[Key]
public Guid UserId { get; set; }
public User User { get; set; }
[Key]
public Guid GroupId { get; set; }
public Group Group { get; set; }
}
Now, the question is; in which tables/classes should I insert the data about the group members?
Is it like this:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
user.GroupMembers.Add(groupMember);
group.GroupMembers.Add(groupMember)
_databaseContext.Users.Update(user);
_databaseContext.SaveChanges();;
_databaseContext.Groups.Update(group);
_databaseContext.SaveChanges();
Or like this, leaving the User and Group untouched, with the information about their relationship ONLY in the join-table:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();

As far as Entity Framework is concerned, this is not a many-to-many relationship
What you have here is three entity types with two one-to-many relationships defined between them. You might know that this is done to represent a many-to-many, but EF doesn't know that.
If I arbitrarily change the names of your entities while maintaining the structure, you wouldn't be able to tell if this was a many-to-many relationship or not.
Simple example:
public class Country {}
public class Company {}
public class Person
{
public int CountryOfBirthId { get; set; }
public virtual Country CountryOfBirth { get; set; }
public int EmployerId { get; set; }
public virtual Company Employer { get; set; }
}
You wouldn't initially think of Person as the represenation of a many-to-many relationship between Country and Company, would you? And yet, this is structurally the same as your example.
Essentially, your handling of your code shouldn't be any different from how you handle any of your one-to-many relationships. GroupMembers is a table (db set) like any else, and EF will expect you to treat it like a normal entity table.
The only thing that's different here is that because GroupMember has two one-to-many relationships in which it is the "many", you therefore have to supply two FKs (one to each related entity). But the handling is exactly the same as if you had only one one-to-many relationship here.
In other words, add your groupMember to the table itself:
GroupMembers groupMember = new GroupMembers
{
// You don't have to fill in the nav props if you don't need them
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
Note: The following only applies to non-Core Entity Framework, as EF Core does not yet support it.
An example of what would be a "real" many-to-many relationship in (non-Core) EF would be if the intermediary table was not managed by you, i.e.:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
}
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
In this scenario, EF will still generate the cross table in the database, but EF will hide this table from you. Here, you are expected to work via the nav props:
var user = myContext.Users.First();
var group = myContext.Groups.First();
user.Groups.Add(group);
myContext.SaveChanges();
Whether you use a "real" many-to-many relationship or manage the cross table yourself is up to you. I tend to only manage the cross table myself when I can't avoid it, e.g. when I want additional data on the cross table.

Make sure the data id is correct and Exists
GroupMembers groupMember = new GroupMembers
{
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
There is less line of code and you have to assume that the object is completely independent when inserted

Related

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

EF - One to many relationship inside another one to many (Facebook posts/reactions)

I have the following entities:
public class User
{
public User()
{
UserName = new Name();
UserEmail = new Email();
}
[Key]
public int Gid { get; set; }
public Name UserName { get; set; }
public Email UserEmail { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public int UserId { get; set; }
public string Content { get; set; }
public string ImageUri { get; set; }
public virtual User Author { get; set; }
}
public class Reaction
{
public int ReactionId { get; set; }
public string Text { get; set; }
public string IconUri { get; set; }
}
One user can have many posts, and one post can have many reactions. The problem is that a reaction should store a reference to its post and the user which reacted. I could make a one to many relationship between users and posts just fine.
How can i map this relationship using Entity Framework?
Addition after comment at the end
If you follow the entity framework code-first conventions for a one-to-many relationship, you don't have to add any attributes nor use fluent API to tell entity framework what you want.
Only if you want different table names, property types, column names, or other specialties about the relations between tables you'll need attributes or fluent API.
You problem is caused because you ommited some of the one-to-many definitions in the class definitions
Your user:
public class User
{
public int Id { get; set; }
// every user has zero or more Posts (one-to-many)
public virtual ICollection<Post> Posts { get; set; }
...
}
The Post:
public class Post
{
public int Id { get; set; }
// every Post belongs to exactly one User using foreign key
public int UserId { get; set; }
public virtual Post Post {get; set;}
// every Post has zero or more Reactins (one-to-many)
public virtual IColleciton<Reaction> Reactions {get; set;}
...
}
Reactions on this Post:
public class Reaction
{
public int Id { get; set; }
// every Reaction belongs to exactly one Post using foreign Key:
public int PostId {get; set;}
public virtual Post Post {get; set; }
...
}
And finally your DbContext:
public MyDbContext : DbContext
{
public DbSet<User> Users {get; set;}
public DbSet<Post> Posts {get; set;}
public DbSet<Reaction> Reactions {get; set;}
}
This is really all that is needed for entity framework to understand that you want the one-to-many relationships and to find out which properties should become foreign key. Entity framework also understands that you can't have a Reaction without a Post. If you try to Remove a Post, all its Reactions will be removed First.
I changed some items to let it be more compliant to the code-first conventinos.
Proper pluralizaion. One Post, many Posts
All IDs are named Id (although your Id is also according to convention). I use this, so it is always clear for every class what the primary key is, even if the class changes name.
All items that won't become columns (like the ICollections) are virtual
all one-to-many have a foreign key with a property name according to the conventions
No classes have a constructor that instantiates members. After all, if you'd do a query the members would be instantiated and immediately replaced by the result of the query
One of the advantages of the ICollections it that you don't need a fairly difficulte left outer join on foreign keys if you want a User-with-his-Posts.
To get all old Users with all or some of their Posts you can use the ICollection. Entity Framework will translate this in the proper Left outer join for you:
var oldUsersWithManyReactions = myDbContext.Users
.Where(user => user.BirthDay < new DateTime(2040, 1, 1))
.Select(user => new
{
// Select only the user properties you plan to use
Id = user.Id,
FullName = user.Name.First + User.Name.Last,
// select the Posts of this User:
RecentPosts = user.Posts
.Where(post.PublicationDate > DateTime.UtcNow.AddDay(-7))
.Select(post => new
{
// again select only the Properties you want to use:
Title = post.Title,
PublicationDate = post.PublicationDate,
ReactionCount = post.Reactions.Count(),
}),
}),
}),
});
Addition after comment
If you want a "User with all his Reactions" use SelectMany. This is in fact a LINQ question and has nothing to do with Entity-Framework
var usersWithAllTheirReactions = myDbContext.Users
.Where (user => ...)
.Select(user => new
{
Id = user.Id,
Name = ...,
AllReactions = user.Posts
.SelectMany(post => post.Reactions)
.Where(reaction => ...)
.Select(reaction => new
{
ReactionDate = reaction.Date,
Text = reaction.Text,
}),
}),
});

Entity Framework added 3 foreign keys for 1 relationship

I have a simple User Class
public class User
{
public int ID { get; set; }
[Required]
public virtual ApplicationUser LoginID { get; set; }
[Required]
public string Name { get; set; }
public string JobTitle { get; set; }
[DefaultValue(UserRole.Standard)]
public UserRole Role { get; set; }
public virtual Company Company { get; set; }
public string Email { get { return LoginID.Email; } }
public bool HasAccess(UserRole TargetRole)
{
//Non-relevant logic
}
}
And I also have a Company class defined as
public class Company
{
public int ID { get; set; }
[Required]
[MaxLength(length: 70)]
public string Name { get; set; }
public virtual ICollection<User> Employees { get; set; }
public virtual ICollection<CompanyEmailDomain> Domains { get; set; }
public ICollection<User> Managers { get { return Employees.Where(x => x.Role == UserRole.Manager).ToList(); } }
}
However, when I run the add-migration command, it tries to add 3 Foreign keys on the User table to the Company table. Can anyone tell me why this would be the case?
AddColumn("dbo.Users", "Company_ID", c => c.Int());
AddColumn("dbo.Users", "Company_ID1", c => c.Int());
AddColumn("dbo.Users", "Company_ID2", c => c.Int());
Entity Framework simply counts the associations between User and Company. It detects three of them:
Company in User.
Employees in Company
Managers in Company
They're all 1-n (Company - User), so, EF concludes, User needs three foreign keys.
You know that Managers is a computed property. In fact, the property shouldn't even be be mapped. You should add the [NotMapped] attribute to it or map it as ignored by the fluent mapping API.
Also, you know that User.Company and Company.Employees are two ends of one association. But because of the two ICollection<User> properties, EF doesn't know which one to choose for the other end (the inverse end) of User.Company.
Now if you unmap Company.Managers, EF will see two properties --User.Company and Company.Employees-- and assume they belong together. So by unmapping one property, only one foreign key will be created.

asp.net mvc - Inserting multiple records in the third table having foreign keys to parent using entity framework 6

I am trying to learn how to use Entity Framework 6 with an already created database, without creating an .edmx file, i.e, using the DbContext and POCO classes.
These are my model classes:
[Table("Category")]
public class Category
{
[Key]
public long CategoryID { get; set; }
public string CategoryName { get; set; }
}
[Table("RegistrationForm")]
public class RegistrationForm
{
[Key]
public int RegistrationID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Country { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
[Table("RegistrationCategory")]
public class RegistrationCategory
{
[Key]
public long RegistrationCategory { get; set; }
public long RegistrationID { get; set; }//Foreign key to RegistrationID in RegistrationForm table in database
public long CategoryID { get; set; }//Foreign key to CategoryID in Category table in database
}
My DbContext class:
public class MyContext : DbContext
{
public virtual DbSet<RegistrationForm> RegistrationForm { get; set; }
public virtual DbSet<Category> Category { get; set; }
public virtual DbSet<RegistrationCategory> RegistrationCategory { get; set; }
}
Here I want to use the default model builder of DbContext.User can select multiple categories in the registration screen so the RegistrationCategory table will have multiple records for each registration. Therefore RegistrationForm and RegistrationCategory are in a one-to-many relationship.
How to write foreign key mappings between the above mentioned models?
How to bind data from Category table data in the mvc view(listbox) so that we can save one record in RegistrationForm table and multiple records in RegistrationCategory table without using loops (using mappings between the c# models) in Entity Framework 6?
The database schema that you have here is a Many to Many relationship between RegistrationForm and Category, with a join table. The RegistrationCategory Table is not necessary to be modeled in Entity Framework at all. You will need to use Entity Framework Fluent API to generate the correct mappings.
First, your RegistrationForm Table:
public class RegistrationForm
{
[Key]
public int RegistrationID { get; set; }
...
// add a navigation property ICollection<Category> to reference the categories
public virtual ICollection<Category> Categories { get; set; }
}
Next, the Category class:
public class Category
{
[Key]
public int CategoryID { get; set; }
public string CategoryName { get; set; }
//Navigation property to reference the RegistrationForms
public virtual ICollection<RegistrationForm> RegistrationForms { get; set; }
}
next, in your DbContext: note the change in pluralization, and the removal of the RegistrationCategory, you do not need a model class for it at all.
public class MyContext : DbContext
{
public virtual DbSet<RegistrationForm> RegistrationForms { get; set; }
public virtual DbSet<Category> Categories { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<RegistrationForm>()
.HasMany(r => r.Categories)
.WithMany(c => c.RegistrationForms)
.Map(
m =>
{
m.MapLeftKey("RegistrationID");
m.MapRightKey("CategoryID");
m.ToTable("RegistrationCategory");
}
);
}
With this in place, you can now query all the Categories of a RegistrationForm or all the RegistrationForms of a Category.
foreach (var category in registrationForm.Categories)
{
//do whatever with each category
}

Multiple one-to-many relationships with entity framework code first

I am trying to form a relationship of 2 tables to a 3rd, on a 1 to many basis. I have the following code:
public class CompanyInvolvement
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
}
public class Person
{
public Person()
{
CompanyInvolvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
public virtual ICollection<CompanyInvolvement> CompanyInvolvements { get; set; }
}
public class Company
{
public Company()
{
Involvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
[Required]
public string CompanyName { get; set; }
public virtual ICollection<CompanyInvolvement> Involvements { get; set; }
}
So effectively a Person can have many involvements in companies and a Company can have many people involved with it. The model builder is used like so:
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Person).WithMany(x => x.CompanyInvolvements);
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Company).WithMany(x => x.Involvements);
I originally created the relationship using the modelbuilder, specifying left and right keys (CompanyId and PersonId) and this worked great. But now I need the Start and End dates for an Involvement, I guess I needed to create a dedicated entity.
The question: When I use the above structure, I can create and read out involvements for a company and also see involvements for a Person. However, when I try to do the following:
var person = _context.People.FirstOrDefault(x => x.Id == personId);
var involvement = company.Involvements.FirstOrDefault(x => x.Person == person );
company.Involvements.Remove(involvement);
_context.SaveChanges();
I get the following error:
A relationship from the 'CompanyInvolvement_Company' AssociationSet is
in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'CompanyInvolvement_Company_Source' must also in the
'Deleted' state.
I think my virtual properties in the 3 entities are correct, but I have the feeling the modelbuilder logic I have may be slightly misconfigured?
I finally figured out what I was doing wrong. I needed to remove the Id property from the CompanyInvolvement entity and add the following composite key:
[Key, Column(Order = 0)]
public Guid PersonId { get; set; }
[Key, Column(Order = 1)]
public Guid CompanyId { get; set; }
I'm guessing by convention, these two properties were then linked as foreign keys to the Person and Company entities respectively. I also removed the modelbuilder mapping as stated in my original question. Once these were done, deleting CompanyInvolvements worked as expected.

Categories

Resources