CRUD operation on many-to-many relationship in ASP.NET MVC

Viewed 78

I have 2 tables in my ASP.NET MVC project: store and product.

The relationship between these tables is many-to-many, so I also have a table StoreProduct.

I want to do CRUD operations on table product, but I need column StoreID from table StoreProduct.

How can I get the storeid from StoreProduct to do CRUD operation on product table?

1 Answers

here is how you'd do it if you want a many-to-many relationship with enums i.e. you want many user roles and work with enums moreover, you can't store it as a flag because you need to know about the roles/privileges without source code(on db side).
TLDR ;) You'd have to create a join table which contains about about who has what privilege(or roles if you want).
There is a Users table which has a list of privileges, a privilege table which has privilege definition i.e. Id, name. And a Join table which will have User and Privilege as it's key. If an entry against this user/privilege combination is present that means this user has this privilege/role.

The code:

//for enum
public enum UserPrivilegeId : int
{
    AddProject = 0,
    ModifyProject = 1,
    DeleteProject = 2,
    AddUser = 3,
    ModifyUser = 4,
    DeleteUser = 5
}

//User class
public record User
{
    public User()
    {
        Privileges = new HashSet<Privilege>();
    }

    public int Id { get; set; }
    public string Username { get; set; }
    public string PasswordHash { get; set; }

    public virtual ICollection<Privilege> Privileges { get; set; }
    public virtual List<UserPrivilege> UserPrivileges { get; set; }
}

//Privilege Class
public record Privilege //note record is IMPORTANT here, because this forces it to compare by value, if you want to *use a class*, then make sure to override GetHashCode and Equals
{
    public Privilege()
    {
        Users = new HashSet<User>();
    }

    public Privilege(UserPrivilegeId privilegeId, string privilegeName)
    {
        PrivilegeId = privilegeId;
        PrivilegeName = privilegeName;
        Users = new HashSet<User>();
    }

    [Key]
    public UserPrivilegeId PrivilegeId { get; set; }
    public string PrivilegeName { get; set; }
    public virtual ICollection<User> Users { get; set; }
    public virtual List<UserPrivilege> UserPrivileges { get; set; } 
}

//and finally the UserPrivilege join class
public record UserPrivilege
{
    public UserPrivilegeId PrivilageId { get; set; }
    public Privilege Privilage { get; set; }

    public int UserId { get; set; }
    public User User { get; set; }
}

//The set-up in dbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Privilege>()
                .HasKey(p => p.PrivilegeId);

    modelBuilder.Entity<Privilege>()
                .Property(p => p.PrivilegeId)
                .HasConversion<int>();


    modelBuilder.Entity<User>()
                .HasMany(user => user.Privileges)
                .WithMany(privilege => privilege.Users)
                .UsingEntity<UserPrivilege>(
                    j => j
                            .HasOne(up => up.Privilage)
                            .WithMany(u => u.UserPrivileges)
                            .HasForeignKey(up => up.PrivilageId),
                    j => j
                            .HasOne(up => up.User)
                            .WithMany(p => p.UserPrivileges)
                            .HasForeignKey(up => up.UserId),
                    j =>
                    {
                        j.Property(u => u.PrivilageId).HasConversion<int>();
                        j.HasKey(u => new { u.PrivilageId, u.UserId });
                    });
    //this adds definitions of privileges to the table
    modelBuilder.Entity<Privilege>()
                .HasData(
                    Enum.GetValues(typeof(UserPrivilegeId))
                    .Cast<UserPrivilegeId>()
                    .Select(p => new Privilege(p, p.ToString())));

    base.OnModelCreating(modelBuilder);
}

Use it by creating a wrapper around it with a boolean on IsActive like this:

public class UserPrivelegesDTO
{
    public UserPrivelegesDTO(UserPrivilegeId privilege, bool isActive)
    {
        this.PrivilegeId = privilege;
        this.PrivilegeName = privilege.ToString();
        this.IsActive = isActive;
    }

    public UserPrivilegeId PrivilegeId { get; set; }
    public string PrivilegeName { get; set; }
    public bool IsActive { get; set; }
}

If you want to convert from List<Privileges> to List<UserPrivilegeDTO>, you can

return await _context.Privileges.OrderBy(x => x.PrivilegeId).ToListAsync(cancellationToken);

To Convert back to List<Privileges>, simply

var privileges = _userPrivilegesViewModel.Privileges.Where(x => x.IsActive).Select(x => new Privilege(x.PrivilegeId, x.PrivilegeName));

If you want to check if the user has privilege

var user = _context.Users.Include(x => x.Privileges).FirstAsync(x => x.Id == 1);
if (request.Editor.Privileges.Any(p => p.PrivilegeId == UserPrivilegeId.ModifyUser))
    return true;

When you want to update privileges

var PrivilegeChangeUser = await 
    _context.Users
            .Include(user => user.Privileges)
            .Include(user => user.UserPrivileges)
            .FirstOrDefaultAsync(user => user.Id == request.UserId);
//**NOTE**you *need* to include the join table i.e. UserPrivileges in order replace the privileges, if you do not include it EF will try to add the privileges which already exist :(

//To update the privileges from an IEnumerable<UserPrivilegeIdEnum>
//first get the privileges objects and add that to users
var AllPrivileges = 
            await _context.Privileges
                .Include(x => x.UserPrivileges)
                .Include(x => x.Users)
                .Where(x => 
                    request.Privileges
                    .Contains(x.PrivilegeId)
            ).ToListAsync(cancellationToken);

PrivilegeChangeUser.Privileges = AllPrivileges;

I have used an enum as the primary key, but you can of course just use a standard int too. For more information read this about how to configure a many-to-many relationship in efcore

Related