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>();
}
}
Related
I have two models that both have a many-to-one relation and a many-to-many relation.
A user can create many alarms but an alarm can only have one creator.
A user can recieve many alarms and alarms have many recievers.
public class AppUser
{
public Guid Id { get; set; }
public virtual IEnumerable<Alarm> CreatedAlarms { get; set; }
[InverseProperty("AlarmRecievers")]
public virtual IEnumerable<Alarm> RecievedAlarms { get; set; }
public AppUser()
{
CreatedAlarms = new HashSet<Alarm>();
RecievedAlarms = new HashSet<Alarm>();
}
}
and
public class Alarm
{
public Guid Id { get; set; }
[ForeignKey("AppUser")]
public Guid? AppUserId { get; set; }
public AppUser AppUser { get; set; }
[InverseProperty("RecievedAlarms")]
public virtual IEnumerable<AppUser> AlarmRecievers { get; set; }
public Alarm()
{
AlarmRecievers = new HashSet<AppUser>();
}
}
but when I try to add a migrations, I get the title as an error. I did expect there to be a created a AlarmReciever table but considering I don't have the model in my code I don't know how to create the key.
Earlier I had
modelBuilder.Entity<AlarmReciever>()
.HasKey(c => new { c.AppUserId, c.AlarmId });
but I'm trying to get rid of the join table in my code so I'd like to do it the way I'm trying to
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));
I came here with an Entity Framework problem which I'm struggling with for some time already. Let's describe it quickly. I have 2 models that are referencing to one model... and I don't really know how can I create the relationship with EF annotations.
First model:
public class ProcessedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
Second model:
public class QueuedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
And the model that I'm referencing to:
public class LogLocation
{
[Key]
public int Id { get; set; }
[ForeignKey("QueuedLog")]
public int QueuedLogId { get; set; }
[ForeignKey("ProcessedLog")]
public int ProcessedLogId { get; set; }
// Some other data
public virtual QueuedLog QueuedLog { get; set; }
public virtual ProcessedLog ProcessedLog { get; set; }
}
As you can see, I've already tried to do something but it's not working properly. I'm getting an error:
LogLocation_ProcessedLog_Source: : Multiplicity is not valid in Role 'LogLocation_ProcessedLog_Source' in relationship 'LogLocation_ProcessedLog'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ''.
LogLocation_QueuedLog_Source: : Multiplicity is not valid in Role 'LogLocation_QueuedLog_Source' in relationship 'LogLocation_QueuedLog'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ''.
It works only when I do typical one-to-one relationship - but this is not what i want.
Btw. this is my first post at StackOverflow so i would like to say hi to everyone! :) You're creating a great community, thanks for all your work!
Edit
The question is: how can i create these models and relationshiph so it would work like this:
I add new ProcessedLog to Db -> It adds a new LogLocation with ProcessedLogId equal to the related ProcessedLog and the QueuedLogId is NULL.
You can do it like this. Database wise I am not sure if this is a great solution... Though it does look like it will work for you.
It worked with EntityFramework 6.1.3 and SQL Server Express.
ProcessedLog.cs
public class ProcessedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
QueuedLog.cs
public class QueuedLog
{
[Key]
public int Id { get; set; }
// Some other data
public virtual LogLocation Location { get; set; }
}
LogLocation.cs
public class LogLocation
{
[Key]
public int Id { get; set; }
// Some other data
public virtual QueuedLog QueuedLog { get; set; }
public virtual ProcessedLog ProcessedLog { get; set; }
}
Context.cs
public class Context : DbContext
{
public DbSet<ProcessedLog> ProcessedLogs { get; set; }
public DbSet<QueuedLog> QueuedLogs { get; set; }
public DbSet<LogLocation> LogLocations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProcessedLog>()
.HasRequired(p => p.Location)
.WithOptional(l => l.ProcessedLog);
modelBuilder.Entity<QueuedLog>()
.HasRequired(p => p.Location)
.WithOptional(l => l.QueuedLog);
}
}
Also the code I tested it with
using (var context = new Context())
{
var processedLog = new ProcessedLog
{
Location = new LogLocation()
};
var queuedLog = new QueuedLog
{
Location = new LogLocation()
};
context.ProcessedLogs.Add(processedLog);
context.QueuedLogs.Add(queuedLog);
context.SaveChanges();
}
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 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.