Question

I am trying to map a one-to-many relationship between users & groups. I want the users to have an individual ranking for each group they join, relative only to that group. Unfortunately EF will not map what I want, and I've been stuck trying to figure out why for 2 days.

IdentityModels.cs:

namespace MapGroupsToRoles.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public ApplicationUser() { this.RolesToGroups = new HashSet<GroupToRoles>(); }
        public virtual ICollection<GroupToRoles> RolesToGroups { get; set; }
        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 GroupToRoles
    {
        public virtual int GroupToRolesId { get; set; }
        public GroupToRoles() { }
        public virtual ApplicationUser User { get; set; }
        public virtual Group Group { get; set; }
        public virtual ClanRole Role { get; set; }
    }
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {

        }

        public System.Data.Entity.DbSet<GroupToRoles> GroupToRolesTable { get; set; }
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
        protected override void OnModelCreating(DbModelBuilder modelbuilder)
        {
            modelbuilder.Entity<ApplicationUser>().ToTable("Users");
        //    modelbuilder.Entity<ApplicationUser>().Ignore(u => u.Id);
            modelbuilder.Entity<ApplicationUser>().HasKey(u => u.Id);           
            modelbuilder.Entity<ApplicationUser>().HasMany(u => u.RolesToGroups).WithRequired().WillCascadeOnDelete(true);

            modelbuilder.Entity<GroupToRoles>().ToTable("GroupToRolesTable").HasKey(u => u.GroupToRolesId);
            modelbuilder.Entity<IdentityUserLogin>().HasKey(u => u.ProviderKey);
            modelbuilder.Entity<IdentityUserRole>().HasKey(u => u.RoleId);

            modelbuilder.Entity<Group>().ToTable("Groups");
            modelbuilder.Entity<Group>().HasKey(u => u.GroupId);
            modelbuilder.Entity<Group>().HasMany(u => u.Roles).WithRequired().WillCascadeOnDelete(true);

        }
    }

}

Above: the important parts to note here are:

public class GroupToRoles
{
    public virtual int GroupToRolesId { get; set; }
    public GroupToRoles() { }
    public virtual ApplicationUser User { get; set; }
    public virtual Group Group { get; set; }
    public virtual ClanRole Role { get; set; }
}

and

public virtual ICollection<GroupToRoles> RolesToGroups { get; set; }

inside the ApplicationUser class.

Group.cs:

namespace MapGroupsToRoles.Models
{
    public class Group
    {
        public int GroupId { get; set; }
        public Group() { this.Roles = new HashSet<GroupToRoles>(); }
        public virtual string GroupName { get; set; }
        public virtual ICollection<GroupToRoles> Roles { get; set; }
    }
}

Right, so what results am I getting? 1. Both Hashsets (ie the GroupToRoles sets in Group.cs and ApplicationUser do not appear... 2. The group table displays Group_GroupId1 and Group_GroupId ... where's the 1 come from?!

Any help would be hugely appreciated!!!!

EDIT_____________

So re-iterate I want to map users to groups, where they have one role per group, and be a member of many groups. Therefore it is a map of one to many between user and "GroupToRoles" class (which in turn maps one to many groups).

Example: John is a member of groups a,b,c for group a he is admin, group b he is a member group c he is a visitor

I don't think I can use (please correct me if im wrong) the standard ASP roles (which is why i created the "ClanRole" class, as I also want a single global role per user which will handle admin rights on the entire site (not just groups the uses have created).

Basically, I want to be able to map the ApplicationUser class to many "GroupToRoles". From there, everything else should fall into place. It is however resisting and I can't work out why!

Many thanks

Was it helpful?

Solution

The steps required to answer this question were far more than anticipated. A guide will be wrote up within the next few weeks, and a link provided here.

A brief overview:

  1. create a seperate context:add to web.config & add the following:

      public class MyContext : DbContext
      {
            public MyContext () : base("DefaultConnection")
            {
            }
            public static MyContext Create()
            {
                return new MyContext ();
            }
      }
    
  2. Remove the ApplicationDbContext, build to quickly find where this is referenced and replace them with your new context

  3. change the fluent to (put inside the context):

        public System.Data.Entity.DbSet<GroupToRoles> GroupToRolesTable { get; set; }
        public System.Data.Entity.DbSet<Group> Groups { get; set; }
        public System.Data.Entity.DbSet<ApplicationUser> Users { get; set; }
        protected override void OnModelCreating(DbModelBuilder modelbuilder)
        {
            modelbuilder.Entity<ApplicationUser>().ToTable("Users");
            modelbuilder.Entity<ApplicationUser>().HasKey(u => u.Id);
            modelbuilder.Entity<ApplicationUser>().HasMany(u => u.RolesToGroups);//.WithRequired(/*u => u.User*/).HasForeignKey(u => u.UserId);
    
            modelbuilder.Entity<GroupToRoles>().ToTable("GroupToRolesTable").HasKey(u => u.GroupToRolesId);
    
            modelbuilder.Entity<IdentityUserLogin>().HasKey(u => u.ProviderKey);
            modelbuilder.Entity<IdentityUserRole>().HasKey(u => u.RoleId);
    
            modelbuilder.Entity<Group>().ToTable("Groups");
            modelbuilder.Entity<Group>().HasKey(u => u.GroupId);
            modelbuilder.Entity<Group>().HasMany(u => u.Roles);//.WithRequired(/*u => u.Group*/).HasForeignKey(u => u.GroupId);
        }
    

OTHER TIPS

I really confused about your idea.

Why can one user assign many groups? (no different with role). This concept makes lots of redundant data.

I think one user shoudl only be able to assign just 1 group, 0-1 to Many.

For example: the application has 3 groups: admin, user, anonym.

  • admin has all role,
  • user have role 'home,usermanager, etc'
  • anonym have role 'home'

You have to change the structure of the entity like this:

IdentityUser

public class IdentityUser<TKey, TLogin, TClaim, TGroup, TGroupRole> : IUser<TKey>
    where TLogin : IdentityUserLogin<TKey>
    where TClaim : IdentityUserClaim<TKey>
    where TGroup : IdentityGroup<TKey, TGroupRole>
    where TGroupRole : IdentityGroupRole<TKey>
{
    public virtual string Email { get; set; }
    public virtual bool EmailConfirmed { get; set; }
    public virtual string PasswordHash { get; set; }
    public virtual string SecurityStamp { get; set; }
    public virtual string PhoneNumber { get; set; }
    public virtual bool PhoneNumberConfirmed { get; set; }
    public virtual bool TwoFactorEnabled { get; set; }
    public virtual DateTime? LockoutEndDateUtc { get; set; }
    public virtual bool LockoutEnabled { get; set; }
    public virtual int AccessFailedCount { get; set; }
    public virtual ICollection<TClaim> Claims { get; private set; }
    public virtual ICollection<TLogin> Logins { get; private set; }
    public virtual TGroup Group { get; set; }
    public virtual TKey Id { get; set; }
    public virtual string UserName { get; set; }
    public IdentityUser()
    {
        this.Claims = new List<TClaim>();
        this.Logins = new List<TLogin>();
    }
}

IdentityGroup

public class IdentityGroup<TKey, TGroupRole> : IGroup<TKey>
    where TGroupRole : IdentityGroupRole<TKey>
{
    public IdentityGroup()
    {
        Roles = new List<TGroupRole>();
    }
    public TKey Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<TGroupRole> Roles { get; private set; }
}

GroupRole

public class IdentityGroupRole<TKey>
{
    public virtual TKey RoleId { get; set; }
    public virtual TKey GroupId { get; set; }
}

IdentityDbContext

public class IdentityDbContext<TUser, TRole, TKey, TUserLogin, TUserClaim, TGroup, TGroupRole> : DbContext
    where TUser : IdentityUser<TKey, TUserLogin, TUserClaim, TGroup, TGroupRole>
    where TRole : IdentityRole<TKey, TGroupRole>
    where TUserLogin : IdentityUserLogin<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TGroup : IdentityGroup<TKey, TGroupRole>
    where TGroupRole : IdentityGroupRole<TKey>
{
    private IdentityConfiguration _config;
    public virtual IDbSet<TUser> Users { get; set; }
    public virtual IDbSet<TRole> Roles { get; set; }
    public virtual IDbSet<TGroup> Groups { get; set; }
    public bool RequireUniqueEmail { get; set; }
    public IdentityDbContext()
        : this("DefaultConnection", new IdentityConfiguration())
    {
    }
    public IdentityDbContext(string nameOrConnectionString)
        : this(nameOrConnectionString, new IdentityConfiguration())
    {
    }
    public IdentityDbContext(string nameOrConnectionString, IdentityConfiguration config)
        : base(nameOrConnectionString)
    {
        _config = config;
    }
    public IdentityDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
        : base(existingConnection, model, contextOwnsConnection)
    {
    }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        if (modelBuilder == null)
        {
            throw new ArgumentNullException("modelBuilder");
        }
        var user = modelBuilder.Entity<TUser>().ToTable(_config.UserTableName, _config.Schema);
        user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId);
        user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
        user.HasOptional(u => u.Group).WithMany().Map(m => m.MapKey("GroupId"));
        user.Property(u => u.UserName)
            .IsRequired()
            .HasMaxLength(256)
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("UserNameIndex") { IsUnique = true }));

        // CONSIDER: u.Email is Required if set on options?
        user.Property(u => u.Email).HasMaxLength(256);

        modelBuilder.Entity<TGroupRole>().HasKey((TGroupRole r) => new
        {
            r.GroupId,
            r.RoleId
        }).ToTable(_config.GroupRoleTableName, _config.Schema);

        modelBuilder.Entity<TUserLogin>().HasKey((TUserLogin l) => new
        {
            l.LoginProvider,
            l.ProviderKey,
            l.UserId
        }).ToTable(_config.LoginTableName, _config.Schema);

        modelBuilder.Entity<TUserClaim>().ToTable(_config.ClaimTableName, _config.Schema);

        var role = modelBuilder.Entity<TRole>()
            .ToTable(_config.RoleTableName, _config.Schema);
        role.Property(r => r.Name)
            .IsRequired()
            .HasMaxLength(256)
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true }));
        role.HasMany(r => r.Groups).WithRequired().HasForeignKey(ur => ur.RoleId).WillCascadeOnDelete();

        var group = modelBuilder.Entity<TGroup>()
            .ToTable(_config.GroupTableName, _config.Schema);
        group.Property(r => r.Name)
            .IsRequired()
            .HasMaxLength(256)
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("GroupNameIndex") { IsUnique = true }));
        group.HasMany(r => r.Roles).WithRequired().HasForeignKey(ur => ur.GroupId).WillCascadeOnDelete();
        //group.HasMany(g => g.Users).WithOptional().Map(m => m.MapKey("GroupId"));
    }
    protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
    {
        if (entityEntry != null && entityEntry.State == EntityState.Added)
        {
            List<DbValidationError> list = new List<DbValidationError>();
            TUser user = entityEntry.Entity as TUser;
            if (user != null)
            {
                if (this.Users.Any((TUser u) => string.Equals(u.UserName, user.UserName)))
                {
                    list.Add(new DbValidationError("User", string.Format(CultureInfo.CurrentCulture, IdentityResources.DuplicateUserName, new object[]
                    {
                        user.UserName
                    })));
                }
                if (this.RequireUniqueEmail && this.Users.Any((TUser u) => string.Equals(u.Email, user.Email)))
                {
                    list.Add(new DbValidationError("User", string.Format(CultureInfo.CurrentCulture, IdentityResources.DuplicateEmail, new object[]
                    {
                        user.Email
                    })));
                }
            }
            else
            {
                TRole role = entityEntry.Entity as TRole;
                if (role != null && this.Roles.Any((TRole r) => string.Equals(r.Name, role.Name)))
                {
                    list.Add(new DbValidationError("Role", string.Format(CultureInfo.CurrentCulture, IdentityResources.RoleAlreadyExists, new object[]
                    {
                        role.Name
                    })));
                }
            }
            if (list.Any<DbValidationError>())
            {
                return new DbEntityValidationResult(entityEntry, list);
            }
        }
        return base.ValidateEntity(entityEntry, items);
    }
}

i just finish my library, full source you can download here

or you can add library via nuget here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top