I'm working with EF and code first. I created a User object which contains a list of the object Role. Each Role object contains a object of type Form. I want to get all roles of a specific user including the "Form" object.
For better understanding: user 1<->n role 1<-> 1 Form
I tried something like that:
DBContext.Users.Include(u => u.Roles.AsQueryable().Include(i=>i.Form)).ToList().Where(r => r.Username.Equals(id)).FirstOrDefault<User>();
This results in:
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
User user = DBContext.Users.Include(u => u.Roles.AsQueryable().Include(i=>i.Form)).ToList().Where(r => r.Username.Equals(id)).FirstOrDefault<User>();
List<Role> roles = user.Roles.AsQueryable().Include(r=>r.Form).ToList();
This doesn't throw any Exception but the Form object is not included in the Role object.
How can I use include on a included object? Is there a way to include "objects" after a linq expression was completed?
Thanks greetings
[Edit] For a better understanding how my model looks
public class User
{
//some properties
public virtual List<Role> Roles { get; set; }
}
public class Role
{
//some properties
public virtual Form Form { get; set; }
}
public class Form
{
//some properties
}
You cannot do an include in another include. Correct way to include a subpath with linq expression is to use select
DBContext.Users.Include(u =>u.Roles.Select(i=>i.Form)).ToList()
Related
I have a bog-standard implementation of Users and Roles using ASP.NET Identity:
public class User : IdentityUserBase { }
public class Role : IdentityRoleBase { }
public class UserRole : IdentityUserRoleBase { }
I need a property or method in the User class that returns the (first) role of the user (in my app, each user can only have one):
public class User : IdentityUserBase
{
// ...
public Role GetRole()
{
return this.Roles.FirstOrDefault(); // this actually returns a UserRole object, with only a UserId and a RoleId available (no Role object)
}
}
Is there a way to do this here in the DAL?
I know it's possible at the UI layer using global objects like RoleManager, DbContext or HTTPContext but I don't have access to those in the User class (and don't want to have to pass them in as arguments).
I would have thought that this must be a standard use-case but I can't find an answer ... my question is basically the same as this one but I need access to the Role information within the User object itself.
First of all, you shouldn't put the GetRole method in the User class, keep those models simple and clean.
To get the roles for a user, you need the db context and the user ID, and you can do this:
var userId = ...; //Get the User ID from somewhere
var roles = context.Roles
.Where(r => r.Users.Any(u => u.UserId == userId))
.ToList();
This worked:
public class UserRole : IdentityUserRoleBase
{
public virtual Role Role { get; set; } // add this to see roles
public virtual User User { get; set; } // add this to see users
}
The User and Role properties are now exposed through the UserRole property that is available through the User object.
Looking back through the evolution of ASP.NET Identity here, these properties were present in IdentityUserRole by default in version 1 but then removed in version 2 in order to support generic primary keys, with a recommendation to use the UserManager class. However, this doesn't help cases like mine where the operations need to be done in the DAL and don't have easy access to this class (or the any of the context classes). Given that I will never have any other type of primary key, it feels quite safe to do this.
I'm trying to build an authorization system not only based in user -> permissions -> roles -> groups but also in entities -> properties.
So i want to limit the model binding in post and put requests so I can verify which properties the user has permission to update or create, and then let him/her update or create the entity ... else reject the request.
Maybe its a idea too complex but I wanted to have the same functionality as some CMS online.
I was reading and maybe this can be solved with a Custom Model Binder, I'm learning a lot about it but, I wanna know if this is "the right path" or maybe there is a faster or better way to do it
Thank you so much, I'll keep updating my question with code so, maybe can help someone in future with the same idea.
I'm working on exactly the same thing, and I do believe it's entirely possible to do this with custom [Attributes]. Here's a small implementation of how I've done it in dynamic select-statements:
Firstly, I have a custom attribute that takes an enum UserRoles as input:
[AttributeUsage(AttributeTargets.Property)]
public class RestrictUserRoles : Attribute
{
public RestrictUserRoles(UserRoles roles)
{
Roles = roles;
}
public UserRoles Roles { get; }
}
The UserRoles-enum can be implemented as such:
[Flags]
public enum UserRoles
{
[Description("Administrator")]
Admin = 1,
[Description("Employee")]
Employee = 2,
[Description("Head of a division")]
DivisionHead = 4,
[Description("Fired")]
Fired = 8
}
I use an enum because some employees can be admin, divisionheads (and even fired).
I then have an IQueryable extension where it gets all the properties that a user is authorized to see and intersects those properties with those selected. To do this I use dynamic Linq and reflection.
public static class QueryableExtensions
{
public static IQueryable SelectProperties<T>(this IQueryable<T> source, UserRoles roles, string criteria)
{
// get all the properties that a user is authorized to see
var authenticatedProperties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(prop => prop.CustomAttributes.Any(attr =>
attr.AttributeType == typeof(RestrictUserRoles) &&
(((RestrictUserRoles) Attribute.GetCustomAttribute(prop, typeof(RestrictUserRoles))).Roles & roles) !=
0))
.Select(prop => prop.Name)
.ToList();
// if there aren't any, then the user is not
// authorized to view any properties
// DISCLAIMER: or someone has forgotten to mark any properties
// with the RestrictUserRoles-attribute
if (!authenticatedProperties.Any()) throw new UnauthorizedAccessException();
// we get all the properties that the user wants to
// select from the string that was passed to the function in
// the form Prop1, Prop2, Prop3
var selectProperties = criteria
.Split(',')
.Select(property => property.Trim());
// Get the intersection between these properties, IE we
// select only those properties that the user has selected
// AND is authorized to view
var properties = authenticatedProperties
.Intersect(selectProperties)
.ToList();
// if there are none that intersect, return all those
// properties that a user is authorized to view
if (!properties.Any()) properties = authenticatedProperties;
// run the query using dynamic linq
return source.Select("new(" + properties.JoinToString(",") + ")");
}
}
This isn't field-tested, but it should do the trick and is easily extendable to mutations.
EDIT: forgot that I use an extension-function to join all properties that I define below:
public static string JoinToString(this IEnumerable<string> list, string delimiter = "")
{
return string.Join(delimiter, list);
}
I have a class named Course, and a user class.
public virtual ICollection<ApplicationUser> Subscribers { get; set; }
This is what my lazy load collection of users looks like,this is a collection in the Course class.
Same goes for the User class which contains a collection of courses.
The relationship between those two is defined as it follows
modelBuilder.Entity<ApplicationUser>().HasMany(z => z.Courses).WithMany();
modelBuilder.Entity<Course>().HasMany(z => z.Subscribers).WithMany();
base.OnModelCreating(modelBuilder);
When I enter the Entity Framework tables for Course and User they do not contain any of those lists that I defined as a properties on those classes,so I cannot user them to save data in them.
Can you please tell me what the reason could be.
Sample of method
var course = db.Courses.Find(id);
var user = userManager.FindByName(User.Identity.Name);
if (!user.Courses.Contains(course))
{
user.Courses.Add(course);
db.SaveChanges();
var item = db.Courses.Where(i => i.Title == course.Title);
return Content(item.First().Title);
---> Verified that course is in the database.
}
After I redirect the user to his courses the list of courses is empty again.
I have a standard DbContext with code like the following:
public DbSet<Interest> Interests { get; set; }
public DbSet<User> Users { get; set; }
I've recently implemented multi-tenancy by creating a TenantContext that contains the following:
private readonly DbContext _dbContext;
private readonly Tenant _tenant;
public TenantContext(Tenant tenant)
: base("name=DefaultConnection") {
this._tenant = tenant;
this._dbContext = new DbContext();
}
public IQueryable<User> Users { get { return FilterTenant(_dbContext.Users); } }
public IQueryable<Interest> Interests { get { return FilterTenant(_dbContext.Interests); } }
private IQueryable<T> FilterTenant<T>(IQueryable<T> values) where T : class, ITenantData
{
return values.Where(x => x.TenantId == _tenant.TenantId);
}
So far, this has been working great. Whenever any of my services creates a new TenantContext, all queries directly off of that context are filtered through this FilterTenant method that guarantees I'm only returning tenant-relevant entities.
The problem that I'm encountering is my usage of navigation properties that do not take this into account:
using (var db = CreateContext()) // new TenantContext
{
return db.Users.
Include(u => u.Interests).FirstOrDefault(s => s.UserId == userId);
}
This query pulls up the tenant-specific Users, but then the Include() statement pulls in Interests for that user only - but across all tenants. So if a user has Interests across multiple Tenants, I get all of the user's Interests with the above query.
My User model has the following:
public int UserId { get; set; }
public int TenantId { get; set; }
public virtual ICollection<Interest> Interests { get; set; }
Is there any way that I can somehow modify these navigation properties to perform tenant-specific queries? Or should I go and tear out all navigation properties in favor of handwritten code?
The second option scares me because a lot of queries have nested Includes. Any input here would be fantastic.
As far as I know, there's no other way than to either use reflection or query the properties by hand.
So in your IQueryable<T> FilterTenant<T>(IQueryable<T> values) method, you'll have to inspect your type T for properties that implement your ITenantData interface.
Then you're still not there, as the properties of your root entity (User in this case) may be entities themselves, or lists of entities (think Invoice.InvoiceLines[].Item.Categories[]).
For each of the properties you found by doing this, you'll have to write a Where() clause that filters those properties.
Or you can hand-code it per property.
These checks should at least happen when creating and editing entities. You'll want to check that navigation properties referenced by an ID property (e.g. ContactModel.AddressID) that get posted to your repository (for example from an MVC site) are accessible for the currently logged on tenant. This is your mass assignment protection, which ensures a malicious user can't craft a request that would otherwise link an entity to which he has permissions (a Contact he is creating or editing) to one Address of another tenant, simply by posting a random or known AddressID.
If you trust this system, you only have to check the TenantID of the root entity when reading, because given the checks when creating and updating, all child entities are accessible for the tenant if the root entity is accessible.
Because of your description you do need to filter child entities. An example for hand-coding your example, using the technique explained found here:
public class UserRepository
{
// ctor injects _dbContext and _tenantId
public IQueryable<User> GetUsers()
{
var user = _dbContext.Users.Where(u => u.TenantId == _tenantId)
.Select(u => new User
{
Interests = u.Interests.Where(u =>
u.TenantId == _tenantId),
Other = u.Other,
};
}
}
}
But as you see, you'll have to map every property of User like that.
Just wanted to offer an alternative approach to implementing multi-tenancy, which is working really well in a current project, using EF5 and SQL 2012. Basic design is (bear with me here...):
Every table in the database has a column (ClientSid binary, default constraint = SUSER_SID()) and is never queried directly, only ever via a dedicated view
Each view is a direct select over the table with WHERE (ClientSid = SUSER_SID()) but doesn't select the ClientSid (effectively exposing the interface of the table)
EF5 model is mapped to the VIEW, not the TABLE
The connection string is varied based on the context of the tenant (user / client whatever multi-tenant partition requirement may be)
That's pretty much it - though it might be useful to share. I know it's not a direct answer to your question, but this has resulted in basically zero custom code in the C# area.
Given a simple POCO class such as:
public class User { public int Id { get; set; } public string Name { get; set; } public string Password { get; set; } }
When I try a query with projection to itself like so:
using (var context = new Context())
{
var user = context.User.Select(u => new User { Id = u.Id }).FirstOrDefault();
}
... I get:
Unhandled Exception: System.ArgumentException: 'Id' is not a member of type 'ORMTest1Model.Users'
... wich comes from the method ValidateMemberInitArgs(...) from System.Linq.Expressions.Expression (use Reflector).
In this method, the type from binding.Member.DeclaringType is of type PocoAdapters.UserAdapter (the generated one) and the type from variable "type" is of type User (the POCO class).
So... for some reason, it's mixing thing up.
Interestingly, if I create a class MyUser which is the exact copy of the poco class User, it works fine and both types at ValidateMemberInitArgs(...) are of type MyUser.
Can anyone reproduce the issue and shed a light on the solution?
Thanks!
(link to same question in project's discussion list: http://code.msdn.microsoft.com/EFPocoAdapter/Thread/View.aspx?ThreadId=2138)
I think the problem is how the class User is being resolved.
To confirm, can you refactor POCO class to be named PocoUser. Does the same error occur?
var user = context.User.Select(u => new User { Id = u.Id }).FirstOrDefault();
would become
var user = context.User.Select(u => new PocoUser { Id = u.Id }).FirstOrDefault();
HTH,
Dan
I think I figured out why this is happening.
MsSql allows for capital letters in table names, but MySql does not.
So if you create your poco adapter from an mssql entity model which contains capital letters in table names, and use it to query a mysql database, you will get this type of error.
In order to fix this, I just renamed my tables to all lowercase.