Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modeling a many-to-many relationship between 3 tables with Code First

I have the following 3 entities:

  • User
  • Account
  • Role

And the relationship goes like

  • One user can have many accounts
  • One account can belong to many users
  • Each user has a role in an account
  • There are a few, predefined roles (defined in the enum Roles)

I got this far:

public class User
{
    public int Id { get; set; }
    public ICollection<Account> Accounts { get; set; }
}

public class Account
{
    public int Id { get; set; }
    public ICollection<User> Users { get; set; }
}

public class Role
{
    public int Id { get; set; }
    public string RoleName { get; set; }
    public virtual ICollection<User> Users { get; set; }
    public virtual ICollection<Account> Accounts { get; set; }
}

Every time an user registers, they also create an account, so I thought of this:

public async Task AddAccountAsync(User user, string accountName)
{
    Account account = new Account(user, accountName);
    Role role = new Role(Roles.Owner);
    Accounts.Add(account);
    user.Accounts.Add(account);
    Entry(user).State = EntityState.Modified;
    await SaveChangesAsync();
 }

But I am not sure how to tie the role to the user to the account, so I can get the role an user has in an account.

For example:

Consider 3 users and 3 accounts:

  • Account1 = (User1, Owner), (User2, TeamMember)
  • Account2 = (User2, Owner)
  • Account3 = (User3, Owner), (User1, ReadOnlyMember)

Following @IvanStoev's answer, I got this:

public async Task AddAccountAsync(User user, string accountName)
{
   Role role = new Role(Roles.Owner);
   Account account = new Account(model.AccountCurrency, model.AccountName, model.Description);
   UserAccount uc = new UserAccount
   {
       User = user,
       Account = account,
       Role = role
   };
   account.UserAccounts.Add(uc);
   Accounts.Add(account);

   user.UserAccounts.Add(uc);
   Entry(user).State = EntityState.Modified;
   await SaveChangesAsync();
}

Should I be saving the UserAccount object (as a DbContext<UserAccount>)?

like image 541
Camilo Terevinto Avatar asked Jul 24 '16 15:07

Camilo Terevinto


People also ask

What is many-to-many relationships in code first approach?

A many-to-many relationship is defined in code by the inclusion of collection properties in each of the entities - The Categories property in the Book class, and the Books property in the Category class: public class Book. { public int BookId { get; set; }

How do you model a many-to-many relationship?

When you have a many-to-many relationship between dimension-type tables, we provide the following guidance: Add each many-to-many related entity as a model table, ensuring it has a unique identifier (ID) column. Add a bridging table to store associated entities. Create one-to-many relationships between the three tables.

What is another way to configure a many-to-many relationship in OnModelCreating method?

Step 1 – Add foreign key property to other entities, in the joining entity. Step 2 – Add collection navigation property on the other entities towards the joining entity. Step 3 – Next create DB Context OnModelCreating() method configure both the foreign keys in the joining entity as a composite key using Fluent API.


1 Answers

  • One user can have many accounts
  • One account can belong to many users
  • Each user has a role in an account

The automatic link table does not work for scenarios like this when you need to associate additional information (Role in this case) with the User - Account link.

So instead of automatic many-to-many association you need to use two one-to-many associations with explicit link entity like this:

Model:

public class User
{
    public int Id { get; set; }
    public ICollection<UserAccount> UserAccounts { get; set; }
}

public class Account
{
    public int Id { get; set; }
    public ICollection<UserAccount> UserAccounts { get; set; }
}

public class UserAccount
{
    public int UserId { get; set; }
    public int AccountId { get; set; }
    public int RoleId { get; set; }
    public User User { get; set; }
    public Account Account { get; set; }
    public Role Role { get; set; }
}

public class Role
{
    public int Id { get; set; }
    public string RoleName { get; set; }
    public ICollection<UserAccount> UserAccounts { get; set; }
}

Configuration:

modelBuilder.Entity<UserAccount>()
    .HasKey(e => new { e.UserId, e.AccountId });

modelBuilder.Entity<UserAccount>()
    .HasRequired(e => e.User)
    .WithMany(e => e.UserAccounts)
    .HasForeignKey(e => e.UserId);

modelBuilder.Entity<UserAccount>()
    .HasRequired(e => e.Account)
    .WithMany(e => e.UserAccounts)
    .HasForeignKey(e => e.AccountId);

modelBuilder.Entity<UserAccount>()
    .HasRequired(e => e.Role)
    .WithMany(e => e.UserAccounts)
    .HasForeignKey(e => e.RoleId);

You can create a new UserAccount in a several ways.

One way is to add it to one of the child UserAccounts collections. In your example, account.UserAccounts.Add(uc); followed by context.Accounts.Add(account) will automatically add it to context.UserAccounts, user.UserAccounts and role.UserAccounts.

Another way is to use context.UserAccounts.Add(uc); in which case it will be automatically added to user, account and role child UserAccounts collections.

like image 141
Ivan Stoev Avatar answered Oct 04 '22 00:10

Ivan Stoev