I'm working on an ASP.NET MVC5 project using EF6. I have 3 models: user, role and permission.
The relation between user and role is many to many. The relation between role and permission is many to many.
LazyLoadingEnabled is disabled in the database context.
public class Permission
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Permission> Permissions { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int ID { get; set; }
public string Username { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class TaskManagerDB : DbContext
{
public TaskManagerDB() : base()
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<Role> Roles { get; set; }
public DbSet<Permission> Permissions { get; set; }
public DbSet<User> Users { get; set; }
}
At an earlier point, I fetch a given user:
User user = db.Users.Find(1);
Now: since I already have the model, how can I load the user roles with their permissions?
I have tried:
db.Entry(user).Collection(x => x.Roles).Query().Include(y => y.Permissions).Load();
But it's not working - user.Roles is still null.
The following solution is not acceptable because I already have the user Model:
User user = db.Users.Include("Roles.Permissions").Where(x => x.ID == 1).FirstOrDefault();
What you have tried
db.Entry(user).Collection(x => x.Roles).Query()
.Include(y => y.Permissions)
.Load();
is indeed the intended way. And it works for everything else except the many-to-many relationship with implicit junction table, which is the case here.
I don't know if it's a bug or "by design", but the solution is to Include the other (calling) end of the relationship, e.g.
db.Entry(user).Collection(x => x.Roles).Query()
.Include(y => y.Users) // <--
.Include(y => y.Permissions)
.Load();
One way of getting the roles and permissions for a user:
db.Roles.Include("Permissions").Where(r => r.Users.Select(u => u.ID).Contains(user.ID));
Related
As you can see here I have two entities that are linked with each other in two ways.
First way is classic many to many by two ICollection and second is by entity GroupRole which specifies role of user for particular group.
The problem is that User stores his roles for every group and Group stores roles of each user for itself.
I'd like to retrieve all groups including it's users but only with particular role, unfortunately it seems like when I use Include statement and then nest inside of it Where I cannot use Group entity representation within nested statement and it throws an error
LINQ statement couldn't be translated because of 'g' within Include statement
If anyone could drop some hint how to work around this problem I would be very glad.
Thanks
var result = await groupsRepository.Query(tracking: false)
.Include(g => g.Users.Where(u => u.GroupRoles.Any(r => r.Role == GroupRolesEnum.ExampleRole && r.GroupId == g.GroupId))).
.ToListAsync();
public class Group
{
public string Name { get; set; }
public Guid GroupId { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<GroupRole> UsersRoles { get; set; }
}
public class User
{
public Guid UserId { get; set; }
public string Email { get; set; }
public ICollection<Group> Groups{ get; set; }
public ICollection<GroupRole> GroupRoles { get; set; }
}
public class GroupRole
{
public Guid UserId { get; set; }
public Guid GroupId { get; set; }
public GroupRolesEnum Role { get; set; }
public virtual Group Group{ get; set; }
public virtual User User { get; set; }
}
I have a table named UserRoles and a user can have multiple Roles.My structure is;
public class UserRole
{
[Key, Column("ID")]
public long Id { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[Column("USERID")]
public long UserId { get; set; }
[ForeignKey("RoleId")]
public Role Role { get; set; }
[Column("ROLEID")]
public long RoleId { get; set; }
}
I want to display them in a grid grouped by userID.Do i need a new data structure?
PS: I am getting data in List<UserRoles> format.
db.UserRoles.Include("User").Include("Role").OrderBy(ur => ur.UserId).ToList();
If you don't need to show User or Role details just remove Include.
EDIT
db.Users.Include("UserRoles.Role").Select(u => new { u.UserId, Roles = string.Join(",", u.UserRoles.Select(ur => ur.Role.RoleId)) }).ToList();
Entity User should have navigation property UserRoles:
public virtual List<UserRole> UserRoles { get; set; }
Normally in EF you can use
ApplicationUser User { get; set; }
to define a users relationship to the property. But I am getting confused how to do it for a property between two users, if that makes sense? Basically I want to define a model for relationships between users e.g.
UserA and UserB has the following relationship:
bool Friends { get; set; }
bool Blocked { get; set; }
I am also wondering how's best to model it for this "Blocked" property. Because I want both to be able to block each other and unblock. e.g. UserA blocks UserB. Then he decides to unblock, and so the Blocked property changes from true to false. BUT if UserB also blocked UserA, the property remains true.
EDIT 28/07
public class ApplicationUser : IdentityUser
{
...
public virtual ICollection<UserRelationships> Relationships { get; set; }
}
public class UserRelationships
{
public int UserRelationshipsId { get; set; }
public UserRelationships() { this.Friends = false; this.Blocked = false; }
public List<ApplicationUser> Users { get; set; }
public bool Friends { get; set; }
public bool Blocked { get; set; }
public string Blocker { get; set; }
public string SecondBlocker { get; set; }
}
You could do a many-to-many (pure join) in EF, but it ONLY tracks Left and Right ID, nothing else. So, if you wanted to audit the time of the friendship or block, you would have to do so in another table.
Instead, I would encourage you to use the following structure:
public class ApplicationUserRelationship
{
public virtual ApplicationUser LeftUser { get; set; }
public virtual ApplicationUser RightUser { get; set; }
public virtual RelationshipType RelationshipType { get; set; }
public virtual DateTime? CreatedDateTime { get; set; }
public virtual DateTime? UpdatedDateTime { get; set; }
...
}
You could of course replace RelationshipType with a string or set of bool as you have done (IsFriend, IsBlocked) or an enum if it makes sense.
In your ApplicationUser you now have:
public virtual ICollection<ApplicationUserRelationship> ApplicationUserRelationships { get; set; }
Then in fluent:
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.ApplicationUserRelationships)
.WithRequired(u => u.LeftUser);
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.ApplicationUserRelationships)
.WithRequired(u => u.RightUser);
I'm using EF 5 rc on VS 2012 RC and got some issues. Pretty sure it's got to do with my knowledge in databases and EF than the version numbers of the software I use :)
So, I have 3 classes. User, Role and Right.
User class
public class User
{
[Key]
public int UserId { get; private set; }
[Required]
public string EmailAddress { get; internal set; }
[Required]
public string Username { get; internal set; }
public ICollection<Role> Roles { get; set; }
// More properties
}
Right Class
public class Right
{
public virtual int RightId { get; set; }
public virtual string Description { get; set; }
}
Role Class
public class Role
{
public virtual int RoleId { get; set; }
public virtual string Description { get; set; }
public virtual ICollection<Right> Rights { get; set; }
}
Context
class MyContext : DbContext
{
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<Right> Rights { get; set; }
}
Now, I want to add roles to a user, and rights to a role. But I also want to make sure it's possible to add the same Right can be added to different roles.
var role1 = new Role()
{
Description = "role1"
};
var role2 = new Role()
{
Description = "role2"
};
var right = new Right()
{
Description = "right"
};
context.Rights.Add(right);
context.Roles.Add(role1);
context.Roles.Add(role2);
role1.Rights = new List<Right>();
role2.Rights = new List<Right>();
role1.Rights.Add(right);
role2.Rights.Add(right);
/**** ERROR ****/
context.SaveChanges();
I'm getting
InvalidOperationException: Multiplicity constraint violated. The role 'Role_Rights_Source' of the relationship 'Role_Rights' has multiplicity 1 or 0..1.
What am I doing wrong ?
Also, I don't feel right about creating a new list like
role1.Rights = new List<Right>();
role2.Rights = new List<Right>();
What's the recommended way to do this ? Rights property is null. So I can't add anything to it without newing it up.
The problem is the convention used by EF to infer the relation. It thinks that the relation is one-to-many but you want many-to-many (role can have multiple rights and the right can be used in multiple roles).
There are two options to solve this:
Option 1: Create navigation property in Right:
public class Right
{
public virtual int RightId { get; set; }
public virtual string Description { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
Now EF convention will detect the collection on both sides of the relation and correctly use many-to-many multiplicity instead of one-to-many
Option 2: Use Fluent-API to tell EF that you want many-to-many relation:
public class MyContext : DbContext
{
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<Right> Rights { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>()
.HasMany(r => r.Rights)
.WithMany();
}
}
Now EF knows that the Right can be assigned to multiple roles even through the Right doesn't have navigation property to Role.
If the Role can be assigned to multiple users you will have to use many-to-many relation as well
Ladislav's Answer should work well for the error.
On your issue of being uncomfortable with using:
role1.Rights = new List<Right>();
You should just initialize the these properties to new Lists in the constructor, then it will be done for all instances of Role:
public class Role
{
public virtual int RoleId { get; set; }
public virtual string Description { get; set; }
public virtual ICollection<Right> Rights { get; set; }
public Role ()
{
this.Rights = new List<Right>();
}
}
I am currently working on my own version of membership using Entity Framework 4.0 and POCO.
After readying Scotts Gu blog post I decided to use conventions as much as possible.
So far I have a class called User:
public class User
{
[System.ComponentModel.DataAnnotations.Key]
public int UserId { get; set; }
[System.ComponentModel.DataAnnotations.StringLength(60)]
public string UserName { get; set; }
public string Password { get; set; }
[System.ComponentModel.DataAnnotations.ConcurrencyCheck]
public string Email { get; set; }
[System.ComponentModel.DataAnnotations.Timestamp]
public byte[] Timestamp { get; set; }
public ICollection<Role> Roles { get; set; }
}
and a class called Role
public class Role
{
[System.ComponentModel.DataAnnotations.Key]
public int RoleId { get; set; }
[System.ComponentModel.DataAnnotations.StringLength(50)]
public string RoleName { get; set; }
[System.ComponentModel.DataAnnotations.StringLength(300)]
public string RoleDescription { get; set; }
public ICollection<User> Users { get; set; }
}
I also implement DbContext like this :
public class BackboneDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
Then based on what Scott suggested I created a Initializer class inheriting from RecreateDatabaseIfModelChanges.
Then in that class I add some dummy items like this:
protected override void Seed(BackboneDbContext context)
{
var roles = new List<Role>
{
new Role
{
RoleId = 0,
RoleName = "User",
RoleDescription = "This role belong to normal users.",
}
};
roles.ForEach(r => context.Roles.Add(r));
var users = new List<User>
{
new User {UserId = 1,
UserName = "Elham",
Email = "abc#yahoo.com",
Password = "xyz",
Roles = new List<Role>
{
roles[0]
}
}
};
users.ForEach(u => context.Users.Add(u));
}
I hope it make sense up to here. If not, basically the above code populate the tables with dummy data if database schema changes.
so far everything is absolutely great. I get the following tables:
Roles, Roles_Users and Users
they all have the info I need, but the problem is when I use the following LINQ query in my Repository class to get all the USers:
public IQueryable<User> GetAllUsers()
{
IQueryable<User> allUsers = from u in _backboneDbContext.Users select u;
return allUsers;
}
now if I check allUsers before passing it to View, I get my users but the Role is set to 'Null'
I don't know why... Any Ideas ? Thank you.
Try making your ICollection properties virtual. This allows EF to do lazy loading of the relationship. Alteratively, look into using the Include method on the query to eagerly load related entities.
You have to add a [System.ComponentModel.DataAnnotations.Association] attribute. For example:
public class User
{
// [...]
[System.ComponentModel.DataAnnotations.Association("User_Roles", "UserId", "RoleId")]
public ICollection<Role> Roles { get; set; }
}
public class Role
{
// [...]
[System.ComponentModel.DataAnnotations.Association("Role_Users", "RoleId", "UserId")]
public ICollection<User> Users { get; set; }
}
If the association is intended to be bi-directional, you must add this attribute on both the User.Roles and the Role.Users properties.