How does List work in EntityFramework Entity? - c#

I have 2 data models:
public class Role
{
int Id;
string Name;
}
and
public class User
{
int Id;
string Name;
List<Role> Roles;
}
there is my DbContext:
public class DatabaseContext : DbContext
{
public DbSet<User> Users => Set<User>();
public DbSet<Role> Roles => Set<Role>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=database.sqlite");
}
}
but i have troubles with List<Role>, when i check db data from viewer, i see that Users table does not have any Roles in users, but Roles table has property UserID, it kinda works but only 1 id stored there, how can i fix it?
I need to have multiple roles
and users that has some of these roles

it kinda works but only 1 id stored there, how can I fix it?
It depends on what you actually want. Depending on the app requirements usually user can be assigned only one role or there is a many-to-many relationship between users and roles.
In case if you want user to have only one role then you should reverse the relationship i.e. role has list of users:
public class Role
{
// ...
public List<User> Users { get; set; } = new();
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
For the many-to-many case there are two main options - specify List on both sides and let EF internally create the join table or explicitly configure the join table (EF Core docs on many-to-many). For the first case your entities can look like the following:
public class Role
{
// ...
public List<User> Users { get; set; } = new();
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public List<Role> Roles { get; set; } = new();
}

Related

Entity Framework Code First: map list of ids to entity using reference table

Let's say I have an existing entity EmailNotification and another entity User. I want my EmailNotification to contain a list of users that it can be sent to. As I see it achievable from database perspective is that we create an additional table like following:
CREATE TABLE UserGroup (UserGroupID INT NOT NULL, UserID INT NOT NULL)
and add a UserGroupID column into EmailNotification.
However, the problem is that I can't think of a way how I can do this using EntityFramework Code First approach, so that I can have a list of Users inside EmailNotification. I want something like
EmailNotification
{
public virtual IEnumerable<User> Users { get; set; }
}
but I don't see how can I do the mentioned mapping using EntityFramework (preferably set it from DbContext, not FluentAPI).
In nutsell what you need I think is to create many to many relation between EmailNotification and User, if the case is that one user can be included in a lot of notifications and one notification can include a lot of users then you need following construct
public class User
{
public int UserId{ get; set; } /*your properties*/
public virtual ICollection<EmailNotification> Courses { get; set; }
}
public class EmailNotification
{
public int EmailNotificationId{ get; set; } /*your properties*/
public virtual ICollection<User> Courses { get; set; }
}
and to customize many to many table creation you can override OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany<EmailNotification>(s => s.EmailNotification)
.WithMany(c => c.User)
.Map(cs =>
{
cs.MapLeftKey("UserId");
cs.MapRightKey("EmailNotificationId");
cs.ToTable("UserEmailNotifications");
});
}
At this case you have many-to-many relation:
MODELS:
public class EmailNotification
{
public int ID { get; set; }
//other stuff...
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int ID { get; set; }
//other stuff...
public virtual ICollection<EmailNotification> EmailNotifications { get; set; }
}
So, EF will implicitley create table: User2EmailNotification with columns: UserID and EmailNotificationID.
P.S. If you, all the same, want to create table UserGroup, it will be
hard (or not comfortable) to access Users from EmailNotification
class, instead you will should to declare UserGroup property inside
this class, so relation between Users and EmailNotifications will be
indirect.

Loading a list of entities containing the parent as a property

I'm having difficulties acchieving certain functionality when using Entity Framework's Code First approach. Using the Fluent API I was trying to accomplish that the Agencies property of a User loads all Agency entities whose Principal or Agent contain him as a user.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public IList<Agency> Agencies { get; set; }
}
public class Agency
{
public int Id { get; set; }
[Required]
public User Principal { get; set; }
[Required]
public User Agent { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasMany(u => u.Agencies).WithRequired(a => a.Agent).WillCascadeOnDelete(true);
modelBuilder.Entity<User>().HasMany(u => u.Agencies).WithRequired(a => a.Principal).WillCascadeOnDelete(true);
}
For some reason only the Agencies whose Principal is set to the owning User are being loaded. Any ideas why or how I could alternatively achieve the functionality I'm asking?
You can't have single collection referenced by two foreign keys.
You have to make 2 collections, and map each of them to one foreign key.
If you want to have a single collection for easier fetching you can do something like this:
public IList<Agency> Agencies1 { get; set; }
public IList<Agency> Agencies2 { get; set; }
[NotMapped]
public string AllAgencies
{
get
{
return Agencies1.Concat(Agencies2).ToList();
}
}
You would probably have to introduce some intermediary class that connects user and agency like:
public class AgencyUser {
public int Id {get;set;}
public Agency Agency {get;set;}
public User User {get;set;}
public UserRole UserRole {get;set;}
}
public enum UserRole {
Principal,
Agent
}
So your agency will have a list of AgencyUser inside, and User.Agencies will be a collection of type AgencyUser.
Otherwise, I believe it will be much less effective in terms of SQL calls.

Entity Framework Code First: Many-to-Many relationship; Remove some Roles from the User

I have a Many-to-Many User/Role relationship.
It’s OK, when I insert a new user into the database and add it in some roles.
But, how to remove some roles from the user? I don't want to completely remove user or role, I only want to:
1. Remove some Roles from the User;
2. Assign new Roles to the User.
My Classes:
public class User
{
public int Id { get; set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
public List<Role> Roles { get; set; }
}
public class Role
{
public int Id { get; set; }
public virtual string Name { get; set; }
public List<User> Users { get; set; }
}
I'm not entirely clear on what you are asking for, but I think it is that you want to be able to remove a role from a user, and that user from the appropriate role?
If so, you will know the user and role, and for this simple case want to make use of the List.Remove function.
void RemUserRole(User u, Role r)
{
u.Roles.Remove(r);
r.Users.Remove(u);
}
This will remove the items from the relevant list of each Role and User.
Edit:
To add new roles to users, you use the List.Add method.
void AddUserRole(User u, Role r)
{
u.Roles.Add(r);
r.Users.Add(u);
}
This simply adds each item to the other's list of roles/users.

Entity Framework and relationships

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>();
}
}

Entity Framework 4 (Linq query doesn't return my roles)

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.

Categories

Resources