I'm assuming this is bread & butter for most..
I have Roles, Permissions, and a RolePermissions entity to model many-to-many relationship.
public class RolePermission
{
[Key]
public int ID { get; set; }
//[Key, ForeignKey("Role"), Column(Order = 0)]
public int RoleID { get; set; }
//[Key, ForeignKey("Permission"), Column(Order = 1)]
public int PermissionID { get; set; }
//Navigational Properties
public virtual Role role { get; set; }
public virtual Permission permission { get; set; }
}
I have an ActionResult to remove a given RolePermission, based on the RoleID and PermissionId that are passed in.
public ActionResult Remove(int Roleid, int Permissionid)
{
RolePermission rolepermission = db.RolePermissions
.Include(p => p.PermissionID == Permissionid)
.SingleOrDefault(p => p.RoleID == Roleid);
db.RolePermissions.Remove(rolepermission);
db.SaveChanges();
return RedirectToAction("Index");
}
The above RolePermission rolepermission.. statement fails with
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path
.. which I'm not suprised, as it has one hell of a code smell to start with.
What I'm after is the equivalent of
Delete from RolePermission
Where RoleID = roleid
AND PermissionID = permissionid
Any guidance?
Many thanks
Since you have the RolePermission exposed as an entity (which is a bit unusual for a many-to-many relationship) you should be able to just query for the record in the join table and delete it:
public ActionResult Remove(int Roleid, int Permissionid)
{
RolePermission rolepermission = db.RolePermissions
.SingleOrDefault(rp => rp.RoleID == Roleid
&& rp.PermissionID == Permissionid);
if (rolepermission != null)
{
db.RolePermissions.Remove(rolepermission);
db.SaveChanges();
}
return RedirectToAction("Index");
}
Related
I have these classes
public class UserRole
{
public int RoleId { get; set; }
public int UserId { get; set: }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
[Table("User")]
public class User
{
public User()
{
UserRoles = new HashSet<UserRole>();
}
public int FirstName { get; set; }
public int UserId { get; set: }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
[Table("Role")]
public class Role
{
public Role()
{
UserRoles = new HashSet<UserRole>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
I am trying to do simple insert into the UserRole table
public class Run
{
DbContext context;
public Run()
{
context = new DbContext();
}
public void Validate(User user, int roleId)
{
InsertUserRole(user,roleId)
context.SaveChanges();
}
}
public void InsertUserRole(user targetUser, int roleId)
{
UserRole targetUserRole = targetUser.UserRoles
.Where(u => u.RoleId == roleId)
.FirstOrDefault();
if (targetUserRole == null)
{
targetUserRole = new UserRole();
targetUserRole.RoleId = roleId;
targetUserRole.UserId = targetUser.UserId;
context.UserRoles.Add(targetUserRole);
}
}
When I am trying to insert into UserRole table, I get an exception
Violation of Unique KEy 'UQ_Role_Name'.Cannot insert duplicate key in dbo.Role
I need to insert row into UserRole as the role does not exist for user, and the role is present in Role table.
Please let me know how I can insert into Userole table using Entity Framework context
I'm not sure if there is enough info about your entities/schema to narrow down the specific cause of your issue, but a few things do come to mind.
Firstly, how are you nominating the PK for the UserRole entity? This should either be done with attributes in the entity or via entity configuration:
public class UserRole
{
[Key, Column(Order=0), ForeignKey("User")]
public int UserId { get; set; }
[Key, Column(Order=1), ForeignKey("Role")]
public int RoleId { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
Next, to add a new UserRole it should be possible to do so by setting the IDs, though generally I recommend setting the navigation properties rather than the IDs. The reasons for this is that this validates the values you are passing, and the entity should be considered "complete" (navigation properties are available) after saving.
For your InsertUserRole method, there are a couple of adjustments there. You don't need to load the existing entity, a simple exists check would be sufficient and faster, then loading the relevant references. This can either be inserted to a UserRole DBSet, or added to the requested user:
public void InsertUserRole(int userId, int roleId)
{
var userRoleExists = context.UserRoles.Any(x => x.UserId == userId && x.RoleId == roleId);
if(userRoleExists) return;
var user = context.Users.Single(x => x.UserId == userId);
var role = context.Roles.Single(x => x.RoleId == roleId);
UserRole userRole = new UserRole
{
User = user,
Role = role
};
context.UserRoles.Add(userRole);
context.SaveChanges();
}
or alternatively:
public void InsertUserRole(int userId, int roleId)
{
var user = context.Users
.Include(x => x.UserRoles)
.Single(x => x.UserId == userId);
if(user.UserRoles.Any(x => x.RoleId == roleId) return;
var role = context.Roles.Single(x => x.RoleId == roleId);
UserRole userRole = new UserRole
{
User = user,
Role = role
};
user.UserRoles.Add(userRole);
context.SaveChanges();
}
I use entity framework core 1.1.
I have a query like below, and I expect to users who have UserProfile by using Include, load UserProfile.
But this query always return UserProfile null .
Query:
var user = dbContext.UserMappers
.Where(e => e.OldUserId == id)
.Select(e => e.User)
.Include(e=>e.UserProfile)
.FirstOrDefault();
Models:
public class UserMapper
{
[Key, ForeignKey(nameof(User))]
public string UserId { get; set; }
public User User { get; set; }
public int OldUserId { get; set; }
}
public class User : IdentityUser
{
public bool Suspended { get; set; }
public string Nickname { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public class UserProfile
{
[Key, ForeignKey(nameof(User))]
public string UserId { get; set; }
public string Name { get; set; }
public string Family { get; set; }
public string Telephone { get; set; }
}
From the EF Core documentation - Loading Related Data - Ignored includes section (highlight is mine):
If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.
This is different from EF6 where Include works on the final query entity type. I don't know if this is a current limitation or "by design", but for now you have to start your queries with the entity requiring includes.
In your case, it should be something like this:
var user = dbContext.Users
// if you don't have inverse navigation property
.Where(e => dbContext.UserMappers.Any(um => um.UserId == e.Id && um.OldUserId == id))
// if you have inverse collection navigation property
//.Where(e => e.UserMappers.Any(um.OldUserId == id))
// if you have inverse reference navigation property
//.Where(e => e.UserMapper.OldUserId == id)
.Include(e => e.UserProfile)
.FirstOrDefault();
I have a set of allowed Roles, and Users who can belong to a number of Roles.
I need to query both the number of Roles, and know from within the User entity to which Roles it belongs.
The first query works, the second query does not (no Roles are returned).
My code is below:
class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Index(IsUnique=true)]
public string Username { get; set; }
public ICollection<Role> Roles { get; set; }
public User()
{
this.Roles = new List<Role>();
}
}
class Role
{
[Key]
public int Id { get; set; }
[Index(IsUnique = true)]
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
class MemberContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
For the below example EF has created 3 databases:
**Roles {Id,Name}**
1, IdentityServerUsers
2, IdentityServerAdministrators
**UserRoles {User_Id,Role_Id}**
5179EF31-C7DD-E311-80BD-00155D458501, 2
**Users {Id,Name}**
5179EF31-C7DD-E311-80BD-00155D458501, admin
I want to be able to run the following queries:
using(var context = new MemberContext())
{
var roles = context.Roles.Where(x => x.Name == "IdentityServerAdministrators").FirstOrDefault();
// roles.Users is empty!!!
var user = context.Users.Where(x => x.Username == "admin").FirstOrDefault();
// user.Roles is empty!!
}
But the roles.Users and user.Roles both return empty, but looking at the above database tables they should have data.
Do I need to explicitly set mapping via EF Code First and what am I doing wrong please?
Update 1
I am now using virtual properties, but still null's are being returned:
class Role
{
[Key]
public int Id { get; set; }
[MaxLength(127)]
[Index(IsUnique = true)]
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Index(IsUnique = true)]
[MaxLength(127)]
public string Name { get; set; }
}
using (var context = new MemberContext())
{
var unf1 = context.Roles.Where(x => x.Name == "IdentityServerAdministrators").FirstOrDefault();
var wow1 = unf1.Users;
// roles.Users is empty!!!
var unf2 = context.Users.Where(x => x.Username == "admin").FirstOrDefault();
var wow2 = unf2.Roles;
// user.Roles is empty!!
}
In your queries, you need to explicitly load the related entities since you're not using lazy loading.
using(var context = new MemberContext())
{
var roles = context.Roles.Include(x => x.Users).Where(x => x.Name == "IdentityServerAdministrators").FirstOrDefault();
// roles.Users is empty!!!
var user = context.Users.Include(x => x.Roles).Where(x => x.Username == "admin").FirstOrDefault();
// user.Roles is empty!!
}
If you want the related entities to be lazy loaded, the navigation properties must be virtual.
Getting the following error upon save of my context:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
The controller code:
#region CreateStory
[HttpPost]
[Authorize]
public ActionResult CreateStory(Userstory story)
{
if (ModelState.IsValid)
{
string _Username = User.Identity.Name.ToLower();
using (var _Db = new NimbleContext())
{
Project _Project = _Db.Projects.First(p => p.Owner.ToLower() == _Username);
if (_Project != null)
{
ProjectGroup _Group = _Db.Groups.First(g => g.GroupID == story.GroupID);
if (_Group != null)
{
_Db.Userstories.Add(story);
_Db.SaveChanges(); <--- Error Occurs here!
return Json(new { Success = true, GroupID = _Group.GroupID });
}
}
}
}
return Json(new { Success = false });
}
#endregion
The Model structure:
Project 1 -> * Groups 1 -> * Userstories
I can prevent the error from occurring by removing the following declarations:
Project _Project = _Db.Projects.First(p => p.Owner.ToLower() == _Username);
ProjectGroup _Group = _Db.Groups.First(g => g.GroupID == story.GroupID);
However I need to make these checks to ensure that the user belongs to the project etc. I really don't see what is causing the error, none of those statements should have an impact on the data to be saved right?
Its all in your model design. If you are explicitly declaring your Foreign Key properties, do them like this:
public class Project {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProjectId { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GroupId { get; set; }
[ForeignKey("Project"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? ProjectId { get; set; }
public virtual Project Project { get; set; }
public virtual ICollection<Userstory> Userstories{ get; set; }
}
public class Userstory {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserstoryId{ get; set; }
[ForeignKey("Group"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? GroupId { get; set; }
public virtual Group Group { get; set; }
}
Note: This should work if you are following Entity Framework Code First, not sure about other methodologies like Model First and Database First
I am using EF 4.1 code first and I am struggling with the association entity and getting the value that was set in the association table. I tried to follow the post on: Create code first, many to many, with additional fields in association table.
My tables are as follows (all are in plural form):
Table: Products
Id int
Name varchar(50)
Table: Specifications
Id int
Name varchar(50)
Table: ProductSpecifications
ProductId int
SpecificationId int
SpecificationValue varchar(50)
My related classes:
public class Product : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSpecification> ProductSpecifications { get; set; }
}
public class Specification : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSpecification> ProductSpecifications { get; set; }
}
public class ProductSpecification
{
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int SpecificationId { get; set; }
public virtual Specification Specification { get; set; }
public string SpecificationValue { get; set; }
}
My context class:
public class MyContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Specification> Specifications { get; set; }
public DbSet<ProductSpecification> ProductSpecifications { get; set; }
protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
}
}
My repository method where I do my call (not sure if it is correct):
public class ProductRepository : IProductRepository
{
MyContext db = new MyContext();
public Product GetById(int id)
{
var product = db.Products
.Where(x => x.Id == id)
.Select(p => new
{
Product = p,
Specifications = p.ProductSpecifications.Select(s => s.Specification)
})
.SingleOrDefault();
return null; // It returns null because I don't know how to return a Product object?
}
}
Here is the error that I am getting back:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmEntityType: : EntityType 'ProductSpecification' has no key defined. Define the key for this EntityType.
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �ProductSpecifications� is based on type �ProductSpecification� that has no keys defined.
What does it mean that no keys are defined? Won't the ProductId and SpecificationId map to Id of Product and Id of Specification respectively?
How would I return a single product with the all the specifications for it?
Entity Framework will recognize that ProductId is a foreign key property for the Product navigation property and SpecificationId is a foreign key property for the Specification navigation property. But the exception complains about a missing primary key ("Key" = "Primary Key") on your ProductSpecification entity. Every entity needs a key property defined. This can happen either by conventions - by a specific naming of the key property - or explicity with data annotations or Fluent API.
Your ProductSpecification class doesn't have a property which EF would recognize as a key by convention: No Id property and no ProductSpecificationId (class name + "Id").
So you must define it explicitely. Defining it with data annotations is shown in the post you linked:
public class ProductSpecification
{
[Key, Column(Order = 0)]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
[Key, Column(Order = 1)]
public int SpecificationId { get; set; }
public virtual Specification Specification { get; set; }
public string SpecificationValue { get; set; }
}
And in Fluent API it would be:
modelBuilder.Entity<ProductSpecification>()
.HasKey(ps => new { ps.ProductId, ps.SpecificationId });
Both ways define a composite key and each of the parts is a foreign key to the Product or Specification table at the same time. (You don't need to define the FK properties explicitely because EF recognizes it due to their convention-friendly names.)
You can return a product including all specifications with eager loading for example:
var product = db.Products
.Include(p => p.ProductSpecifications.Select(ps => ps.Specification))
.SingleOrDefault(x => x.Id == id);