User has roles for systems - how to (object-)model

2019-01-29 12:36发布

问题:

I've got the following Entities:

Privilege ( Id, Name )
Role ( Id, Name, ICollection<Privilege> )
System ( Id, Name )

User ( Id, Name, Pass, ? )

Now I want to model "A user may have for each of zero or more systems zero or more roles", e.g.:

IDictionary<System, ICollection<Role>> SystemRoles { get; set; }

Is this possible with ASP.NET EntityFramework? If yes, how? What attributes do I have to set?

Been looking around for quite some time now, however I don't find anything useful on the net for "entity framework code first ternary relation"

Can you point me to some nice link where this is covered in detail? Or maybe give me a hint how to model it / which attributes I can put on the dictionary?

Additional question: If the IDictionary solution works somehow, is there any change to get change tracking proxy performance? IDictionary is not an ICollection...

回答1:

A user may have for each of zero or more systems zero or more roles

You will need an entity that describes the relationship between the three:

public class UserSystemRole
{
    public int UserId { get; set; }
    public int SystemId { get; set; }
    public int RoleId { get; set; }

    public User User { get; set; }
    public System System { get; set; }
    public Role Role { get; set; }
}

I would create a composite primary key from all three properties because each combination may only occur once and must the unique. Each part of the key is a foreign key for the respective navigation property User, System and Role.

Then the other entities would have collections refering to this "link entity":

public class User
{
    public int UserId { get; set; }
    //...
    public ICollection<UserSystemRole> UserSystemRoles { get; set; }
}

public class System
{
    public int SystemId { get; set; }
    //...
    public ICollection<UserSystemRole> UserSystemRoles { get; set; }
}

public class Role
{
    public int RoleId { get; set; }
    //...
    public ICollection<UserSystemRole> UserSystemRoles { get; set; }
}

And then the mapping with Fluent API would look like this:

modelBuilder.Entity<UserSystemRole>()
    .HasKey(usr => new { usr.UserId, usr.SystemId, usr.RoleId });

modelBuilder.Entity<UserSystemRole>()
    .HasRequired(usr => usr.User)
    .WithMany(u => u.UserSystemRoles)
    .HasForeignKey(usr => usr.UserId);

modelBuilder.Entity<UserSystemRole>()
    .HasRequired(usr => usr.System)
    .WithMany(s => s.UserSystemRoles)
    .HasForeignKey(usr => usr.SystemId);

modelBuilder.Entity<UserSystemRole>()
    .HasRequired(usr => usr.Role)
    .WithMany(r => r.UserSystemRoles)
    .HasForeignKey(usr => usr.RoleId);

You can remove one of the collection properties if you don't need them (use WithMany() without parameter then).

Edit

In order to get a dictionary for a user you could introduce a helper property (readonly and not mapped to the database) like so:

public class User
{
    public int UserId { get; set; }
    //...
    public ICollection<UserSystemRole> UserSystemRoles { get; set; }

    public IDictionary<System, IEnumerable<Role>> SystemRoles
    {
        get
        {
            return UserSystemRoles
                .GroupBy(usr => usr.System)
                .ToDictionary(g => g.Key, g => g.Select(usr => usr.Role));
        }
    }
}

Note that you need to load the UserSystemRoles property with eager loading first before you can access this dictionary. Or alternatively mark the UserSystemRoles property as virtual to enable lazy loading.