I have a users and roles table. A user can have multiple roles.
I want to grab all of the users without a specific role. The problem is if a user has 2 roles, one being the role we don't want, the user will still be returned.
public IList<User> GetUserByWithoutRole(string role)
{
return CreateQuery((ISession session) => session.CreateCriteria<User>()
.CreateAlias("Roles", "Roles")
.Add(!Restrictions.Eq("Roles.RoleDescription", role))
.List<User>());
}
The only solution I came up with was client side
public IEnumerable<User> GetUserByWithoutRole(string role)
{
return CreateQuery((ISession session) => session.CreateCriteria<User>()
.CreateAlias("Roles", "Roles")
.Add(!Restrictions.Eq("Roles.RoleDescription", role))
.List<User>()).Where(u => u.Roles.FirstOrDefault(r => r.RoleDescription == role) == null);
}
Anyone know of a better solution? Thanks!
Alternatively you can use Criteria API to create subquery
var subquery = DetachedCriteria.For<Role>("role");
subquery.Add(Restrictions.EqProperty("role.User.id", "user.id"))
.SetProjection(Projections.Property("role.RoleDescription"));
var users = session.CreateCriteria<User>("user")
.Add(Subqueries.NotIn(role, subquery))
.List<User>();
If you want to do that in nhibernate you'll have to do two queries or a subquery. Here is a similar post:
FluentNHibernate query on many-to-many relationship objects
Thanks, for anyone interested this is what I finished up with:
public IEnumerable<User> GetUserByWithoutRole(string role)
{
var subQuery = DetachedCriteria.For<User>()
.CreateAlias("Roles", "Roles")
.SetProjection(Projections.Property("UserID"))
.Add(Restrictions.Eq("Roles.RoleDescription", role));
return _session.CreateCriteria<User>()
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.Add(Subqueries.PropertyNotIn("UserID", subQuery))
.AddOrder(Order.Asc("FirstName"))
.List<User>();
}
Related
Normally in SQL we can write this query UPDATE users SET isAdult = 1 WHERE age >18
I want to apply some edit to all rows that satisfy some condition in entity framework core.
I wrote this code and I got an error
List<User> usersList = _context.Users.Where(u => u.age >18).ToList();
usersList.ForEach(a =>
{
a.isAdult = 1;
});
_context.Entry(usersList).State = EntityState.Modified;
_context.SaveChanges();
The error is
System.InvalidOperationException: The entity type 'List' was not
found. Ensure that the entity type has been added to the model. at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetOrCreateEntry(Object
entity) at
Microsoft.EntityFrameworkCore.DbContext.EntryWithoutDetectChanges[TEntity](TEntity
entity) at
Microsoft.EntityFrameworkCore.DbContext.Entry[TEntity](TEntity entity)
I made this update but I want to know if this is the best way.
List<Users> usersList = _context.Users.Where(u => u.age >18).ToList();
usersList.ForEach(a =>
{
a.isAdult = 1;
_context.Entry(a).State = EntityState.Modified;
_context.SaveChanges();
});
The error was because the list isn't defined as an EF Entity.
In the end, you don't need to modify the state youself.
List<User> usersList = _context.Users.Where(u => u.age >18).ToList();
usersList.ForEach(a => { a.isAdult = 1; });
_context.SaveChanges();
I've created a user and attached to him a role that has a number of claims. The problem is I don't see a direct way to access retrieve them using Entity Framework Core and Identity integration. Here's what I'd like to do ideally:
return _context.Users
.Include(u => u.Roles)
.ThenInclude(r => r.Role)
.ThenInclude(r => r.Claims)
But there's not Role property, just RoleId. So I can not Include role claims. Of course I get make a separate query to get claims or even use RoleManager:
var user = _context.Users.Single(x => x.Id == ...);
var role = _roleManager.Roles.Single(x => x.Id == user.Roles.ElementAt(0).RoleId);
var claims = _roleManager.GetClaimsAsync(role).Result;
but it looks inefficient and even ugly. There should be a way to make a single query.
My last hope was Controller.User property (ClaimsIdentity). I hoped it somehow smartly aggregates claims from all the roles. But seems like it doesn't...
You can use SQL-like query expressions and get all claims from all roles of a user like this:
var claims = from ur in _context.UserRoles
where ur.UserId == "user_id"
join r in _context.Roles on ur.RoleId equals r.Id
join rc in _context.RoleClaims on r.Id equals rc.RoleId
select rc;
You can add navigation properties.
public class Role : IdentityRole
{
public virtual ICollection<RoleClaim> RoleClaims { get; set; }
}
public class RoleClaim : IdentityRoleClaim<string>
{
public virtual Role Role { get; set; }
}
Then you have to configure your identity db context:
public class MyIdentityDbContext : IdentityDbContext<User, Role, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, RoleClaim, IdentityUserToken<string>>
Usage:
await _context.Roles.Include(r => r.RoleClaims).ToListAsync();
At the end it generates the following query:
SELECT `r`.`Id`, `r`.`ConcurrencyStamp`, `r`.`Name`, `r`.`NormalizedName`, `r0`.`Id`, `r0`.`ClaimType`, `r0`.`ClaimValue`, `r0`.`RoleId`
FROM `roles` AS `r`
LEFT JOIN `role_claims` AS `r0` ON `r`.`Id` = `r0`.`RoleId`
ORDER BY `r`.`Id`, `r0`.`Id`
Source: Identity model customization in ASP.NET Core
Make sure you are adding the roles and claims correctly. Below is an example of how I create a user and add claims and roles.
private async Task<IdentityResult> CreateNewUser(ApplicationUser user, string password = null){
//_roleManger is of type RoleManager<IdentityRole>
// _userManger is of type UserManager<ApplicationUser>
//and both are injected in to the controller.
if (!await _roleManger.RoleExistsAsync("SomeRole")){
await _roleManger.CreateAsync(new IdentityRole("SomeRole"));
}
var result = password != null ? await _userManager.CreateAsync(user, password) : await _userManager.CreateAsync(user);
if(result.Succeeded) {
await _userManager.AddToRoleAsync(user, "SomeRole");
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Name, user.Email));
}
return result;
}
Then you can use the _userManager to get the claims. This is how I get the current user using _userManager. Then you can just call something like this:
var claims = await _userManager.GetClaimsAsync(user);
We're using NHibernate for our membership system. A User can be a member of many Roles and a Role can have many users.
When a Role or a User is deleted, it should only cascade the delete of the association record ("RoleUsers" table).
Deleting a Role works as expected. However, deleting a User does not delete the association record and as such fails due to a foreign key constraint.
My mapping on the Role side:
HasManyToMany(r => r.Users)
.Access.CamelCaseField()
.Table("RoleUsers")
.ParentKeyColumn("RoleId")
.ChildKeyColumn("UserId")
.AsSet();
Mapping on the User side:
HasManyToMany(u => u.Roles)
.Access.CamelCaseField()
.Table("RoleUsers")
.ParentKeyColumn("UserId")
.ChildKeyColumn("RoleId")
.Inverse(); // we'll add user to role, not role to user
And the failing test:
[Test]
public void Deleting_user_should_not_delete_roles()
{
var user = new User("john#doe.com", "John", "Doe", "Secr3t");
var role = new Role("Admin");
role.AddUser(user);
object id;
using (var txn = Session.BeginTransaction())
{
id = Session.Save(user);
Session.Save(role);
txn.Commit();
}
Session.Clear();
var fromDb = Session.Get<User>(id);
using (var txn = Session.BeginTransaction())
{
Session.Delete(fromDb);
txn.Commit();
}
Session.Query<Role>().Count().ShouldEqual(1);
}
I've tried every combination of Cascade on the user mapping and it either fails or deletes the association record AND the role (not what I want).
Inverse and cascading are two different concepts. And of course, both are supported on <many-to-many> relation. See the documentation 6.8 (scroll down almost to 6.9)
http://nhibernate.info/doc/nh/en/index.html#collections-bidirectional
1) Firstly, we can remove the inverse setting of the User.Roles collection. This will straightens the behavior of the Users`s relations to Roles, and force their deletion before User itself is deleted.
2) Secondly. If the Roles collection of the User is marked as inverse,
HasManyToMany(u => u.Roles)
...
.Inverse(); // we'll add user to role, not role to user
deletion of any User, will never trigger deletion of the pair. That's because we are explicitly saying: the one and only one who care about the relation is the Role.
So if we would like to continue in your scenario:
.Inverse(); // we'll add user to role, not role to user
we should be consitent. "we'll remove user from roles, not roles from user":
[Test]
public void Deleting_user_should_not_delete_roles()
{
var user = new User("john#doe.com", "John", "Doe", "Secr3t");
var role = new Role("Admin");
role.AddUser(user);
object roleId;
object id;
using (var txn = Session.BeginTransaction())
{
id = Session.Save(user);
roleId = Session.Save(role);
txn.Commit();
}
Session.Clear();
// take both from DB
var userFromDb = Session.Get<User>(id);
var roleFromDb = Session.Get<Role>(roleId);
using (var txn = Session.BeginTransaction())
{
// HERE just remove the user from collection
roleFromDb.Users.Remove(userFromDb);
// all relations will be deleted
Session.Save(roleFromDb);
txn.Commit();
}
...
// assertions
// "John's" relation to Role "admin" is deleted
}
NOTE:
3) Cascade was not used in there, but could help to reduce Session.Save(user)...
EDIT: Extending the point 3)
Deletion of the user as Ben Foster noticed in a comment.
3) We should even allow the Role to manage its Users collection completely. Let's introduce the casdace="all" (casdace="all-delete-orhpan" if User without any Role should be deleted at all). Now we can add/update users only via Role object.
the mapping of the Role's Users collection should look like:
HasManyToMany(r => r.Users)
.Access.CamelCaseField()
.Table("RoleUsers")
.ParentKeyColumn("RoleId")
.ChildKeyColumn("UserId")
//.Cascade.All(); // just save or update instance of users
.Cascade.AllDeleteOrphan(); // will even delete User without any Role
.AsSet();
Having inverse and cascade we can adjust the test:
[Test]
public void Deleting_user_should_not_delete_roles()
{
var user = new User("john#doe.com", "John", "Doe", "Secr3t");
var role = new Role("Admin");
role.AddUser(user);
object roleId;
using (var txn = Session.BeginTransaction())
{
// I. no need to save user
roleId = Session.Save(role);
...
And later call this to get rid of a User
...
using (var txn = Session.BeginTransaction())
{
var user = Session.Get<User>(id);
var roles = user.Roles.ToList();
roles.ForEach(role => role.RemoveUser(user))
// II. not only relations, but even the User is deleted
// becuase updated roles triggered delete orhpan
// (no Session.Update() call there)
txn.Commit();
}
I have the following index I'm creating in order to get all the permissions for a specific user. In the transform, roles.SelectMany(x => x.Permissions) could contain duplicates, so I want to put .Distinct() on it. However, when I do, it seems to get translated to Enumerable.Distinct(roles.SelectMany(x => x.Permissions) inside of Raven, which returns no results. If I change the index directly in Raven to use .Distinct() instead of Enumerable.Distinct(...), it works perfectly.
How can this be written so that it gets translated properly in Raven?
public class PermissionsByUser : AbstractIndexCreationTask<User, UserWithPermissions>
{
public override string IndexName
{
get
{
return "Users/PermissionsByUser";
}
}
public PermissionsByUser()
{
Map = users => from user in users
from role in user.Roles
select new {role.Id};
TransformResults = (database, users) => from user in users
let roles = database.Load<Role>(user.Roles.Select(x => x.Id))
select new
{
Id = user.Id,
Username = user.Username,
Password = user.Password,
Roles = user.Roles,
Permissions = roles.SelectMany(x => x.Permissions)//.Distinct()
};
}
}
This was, I think, actually just a problem of stale results. Answered at https://groups.google.com/forum/?fromgroups#!topic/ravendb/0hO8TOQicwc
I have two classes, user and role, defined as:
public class User : Entity
{
// other properties ...
public virtual string Username
public virtual ICollection<Role> Roles { get; set; }
}
public class Role : Entity
{
public virtual string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
In my mapping code, I have the following:
mapper.Class<User>(map =>
{
map.Bag(x=>x.Roles,
cm=>
{
cm.Table("UserRole");
cm.Cascade(Cascade.All);
cm.Key(k => k.Column("[User]"));
},
em=>
{
em.ManyToMany(mm =>
{
mm.Column("[Role]");
});
});
});
mapper.Class<Role>(map =>
{
map.Bag(x=>x.Users,
cm=>
{
cm.Inverse(true);
cm.Table("UserRole");
cm.Key(k=>k.Column("[Role]"));
},
em =>
{
em.ManyToMany(mm =>
{
mm.Column("[User]");
});
});
});
The mappings generate the expected schema, but the join table is never populated. Adding a new user with a new Role in its collection persists the role and then the user to the appropriate tables, but the join table is left empty. Why?
Edit: I still have not made any progress on this. I'm absolutely sure the mapping is correct, and the correct schema is generated, but the join table simply isn't populated. For test purposes, I'm generating entities using NBuilder like so:
var roles = new Role[]
{
new Role("Admin"),
new Role("Manager"),
new Role("User")
};
var users = Builder<User>.CreateListOfSize(10)
.TheFirst(1)
.Do(x =>
{
x.Roles.Add(roles[0]);
x.Roles.Add(roles[1]);
roles[0].Users.Add(x);
roles[1].Users.Add(x);
})
.All()
.With(x => x.Id = 0)
.And(x => x.Version = 0)
.And(x => x.Username = "test user")
.And(x => x.Password = "test password")
.Do(x =>
{
x.Roles.Add(roles[2]);
roles[2].Users.Add(x);
}
.Build();
foreach (var u in users) session.Save(u);
The User and Role entities are persisted correctly, but the join table remains empty. This means I cannot effective query the roles for a given user later, which nullifies the point.
Make sure you have both classes referencing each other.
I think that code, similar to one below, should work for you:
role.Users.Add(user);
user.Roles.Add(role);
session.Save(user); // NH only saves user and role, so it can get auto-generated identity fields
session.Flush(); // NH now can save into cross-ref table, because it knows required information (Flush is also called inside of Transaction.Commit())
I found a good answer to a question about many-to-many with lot of explanations and quotes from NH documentation. I think it worth to read it.
[EDIT]
In answer to this somewhat similar question there is discussion in which need for explicit transaction to save into cross-table is mentioned.
I also edited code above with adding session.Flush() to reflect my findings.
I ended up downloading the NHibernate source and referencing that directly so I could step through it. It turns out that it had something to do with the fact that my code for generating the test data was not wrapped in an explicit session transaction. Once I added that, it was fine. I'd love to see some kind of explanation on this, as I wasn't able to follow the code very clearly, but I'm at least satisfied that the problem is solved.