Virtual Navigation Properties and Multi-Tenancy - c#

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.

Related

Can't configure lazy loading in EF Core 2.2 to cut off unloaded parts

I'm getting the following content when I invoke my API. It kind of breaks up in the middle when the tenant entity that member is linked to, will start listing its member entities.
{
"id":"00000000-7357-000b-0001-000000000000",
"tenantId":"00000000-7357-000a-0001-000000000000",
"userName":"user1",
"tenant":{
"id":"00000000-7357-000a-0001-000000000000",
"name":"First Fake Org",
"members":[
I configured the lazy loading like this.
services.AddDbContext<Context>(config => config
.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetConnectionString("Register")));
How should I change the code so that the lazily loaded entities don't get served? I was hoping that it would simply return an empty list to the client. Should I use a DTO for that purpose and not return from the DB like this? There's talk about not using lazy loading for APIs at all here.
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
Member output;
output = Context.Members
.Include(e => e.Tenant)
.Single(e => e.UserName == userName);
return Ok(output);
}
I'm not sure what to google for and all the hits I got were pointing to the UseLazyLoadingProxies() invokation.
This will probably be somewhat long winded: But here goes.
It sounds like you have Entities which look something like:
public partial class Member
{
public virtual long Id { get; set; }
public virtual List<Tenant> Tenants { get; set; } //tables have fk relationship
}
public partial class Tenant
{
public virtual long Id { get; set; }
public virtual List<Member> Members{ get; set; } //tables have another fk relationship?
}
And then for this method:
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
Member output;
output = Context.Members
.Include(e => e.Tenant)
.Single(e => e.UserName == userName);
return Ok(output);
}
I see a few issues, but I'll try to keep it short:
I wouldn't have the controller do this directly. But it should work.
What I think you over looked is exactly what the .Include statement does. When the object is instantiated, it will get all of those related entities. Includes essentially converts your where statement to a left join, where the foreign keys match (EF calls these navigation properties).
If you don't want the Tenant property, then you can omit the .Include statement. Unless this is meant to be more generic (In which case, an even stronger reason to use a different pattern and auto mapper).
Hopefully your database doesn't truly have a FK relationship both ways, if it does, fix that ASAP.
The next issue is that you might not want a list of child properties, but it is in the model so they will be "there". Although your List Tenants might be null. And while this might be fine to you, right now. As a general rule when I see an API returning a property, I expect something to be either not there (This Member doesn't have tenants) or something is wrong, like perhaps there is a second parameter I missed. This probably isn't a problem 93.284% of the time, but it is something to be mindful of.
This starts to get into why an AutoMapper is great. Your Database Models, Business Models and views are likely different. And as much as you shouldn't return the database models directly. Taking control of how the data is represented for each part of the application is a great idea.
You could reduce the code easily, and remove the navitation properties:
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
return Ok(Context.Members
.Include(e => e.Tenant)
.Single(e => e.UserName == userName));
}
But again, a business layer would be better:
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
return Ok(MemberRepository.GetMember(userName));
}
The main point I'd stress though, is creating a view model.
For example, Let's say a user Detail:
public class MemberDetail
{
public string UserName {get; set;}
public long UserId { get; set; }
public string FullName { get; set; }
}
This way the view always receives exactly what you want to see, and not the extra data. Add this with the fact that you can know exactly how every use of Member to MemberDetail will map.

Retrieve Role for a User in the DAL using ASP.NET Identity

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.

Entity Framework unit test (or similar) to ensure that when using a DbSet, an particular extension method is always called

I want to enforce a requirement that any time you interact with db.Users, you are REQUIRED to call a particular extension method. Below is some more specific information.
When interacting with my database via Entity Framework, I have the following query (db is a DbContext instance):
var user = db.Users
.FromOrganisation(someId)
.FirstOrDefault(u => u.Username == someUsername);
This query will vary all of the time throughout the application (WHERE clause will often be different etc.). Because of this, I want to make it a requirement that you must always call the FromOrganisation() extension method so that all data returned is filtered by Organisation first.
This is to prevent anyone ever seeing data belonging to a different organisation, but I'm stuck on exactly how to achieve it.
Is there a unit test I can write to alert the developer that the Users DbSet is being used without filtering by organisation? If not, are there any alternative routes I could take to achieve the same level of protection.
In case it's important, the extension method itself looks like this:
public static IQueryable<User> FromOrganisation(this IQueryable<User> u, int organisationId)
{
return u.Where(x => x.OrganisationId == organisationId);
}
My final solution
I changed my context as follows:
public DbSet<User> Users { get; set; }
Became:
[Obsolete("MUST use service!")]
public DbSet<User> UsersUnfiltered { get; set; }
public IQueryable<User> Users(int id)
{
#pragma warning disable 618
return UsersUnfiltered.Where(x => x.OrganisationId == id);
#pragma warning restore 618
}
What this does is encourages you to use the Users method to return a filtered list of Users by organisation. This can be further filtered, joined, and queried etc as normal.
You also have access to the UsersUnfiltered DbSet if you need it, but if you use this then a compiler warning is generated. You can suppress this warning by accessing it inside of the #pragma warning disable 618 directives.
With this in place, you have the code on your side to prevent you ever using the Users data without filtering by organisation, unless you really mean to do it.
Thanks to #mark_h for helping me reach this solution.
An issue with what you are proposing is that if you could enforce its use, you would have to enforce it every time you wanted to sub-query a set of users even after you had done your initial query from the context i.e.
var allUsersOfOrganisation1 = db.Users.FromOrganisation(1)
// some code...
var someUsersOfOrganisation1 = allUsersOfOrganisation1.FromOrganisation(1).Where(...)
If you enforced the use of your extension method how would you differentiate between a query to a set containing all users and a filtered subset?
You can achieve the result you are after by wrapping your entity model and only exposing the Users through methods that enforce the unique organisation id;
public class WrappedEntities
{
private MyEntityFramework Entities = new MyEntityFramework();
//regular public entities
public DbSet<Organisation> Organisations { get { return Entities.Organisation; } set { Entities.Organisation = value as DbSet<Organisation>; } }
//Special case
public IQueryable<User> Users(int id)
{
return Entities.Users.Where(x => x.OrganisationId == organisationId);
}
//other wrapped methods...
public int SaveChanges()
{
return Entities.SaveChanges();
}
}
If you gave this class an interface you would also be able to mock it for the purposes of unit testing.

Remove access to certain Database Records to a user using Custom Filter MVC

Hi guys I have various Database Models like this one
public class Trips
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public .... // and finally
public string Group_Of_user { get; set; }
}
And my Identity profile is like this
public class ApplicationUser : IdentityUser
{
public string Group_Of_user { get; set; }
}
I had originally designed my models without and Group_of_user.My main aim is to restrict one Group_of_user from another Group_of_user records. So now everytime I have to check the record everytime the User is accessing, editing etc etc with a if statement like the one below.
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = manager.FindById(User.Identity.GetUserId());
if (entry.Group_Of_user != user.Group_Of_user)
{
return HttpNotfound();
}
Is there any way either with a action filter or some code magic in Identity by which I can do this auto magically without inserting too many Ifs in my controller.
Sure. You just create a custom action filter, and then apply it to either your controller, action, or even globally via App_Start\FilterConfig.cs. However, this is best left as an excercise for you, as if you don't understand the mechanics, you won't understand how to fix problems, should they arise. Besides, the MSDN documentation is actually pretty good on this.
However, before you go too far, this is actually a pretty poor way of restricting access, at least in the sense of maintainability. Instead of using some arbitrary string value, actually create a foreign key to an instance of your user entity or IndentityRole. Or, if you don't want to do either or those, at least create a full-fledged Group object and create an association with that.

Filtering navigation properties in EF Code First

I'm using Code First in EF. Let's say I have two entities:
public class Farm
{
....
public virtual ICollection<Fruit> Fruits {get; set;}
}
public class Fruit
{
...
}
My DbContext is something like this:
public class MyDbContext : DbSet
{
....
private DbSet<Farm> FarmSet{get; set;}
public IQueryable<Farm> Farms
{
get
{
return (from farm in FarmSet where farm.owner == myowner select farm);
}
}
}
I do this so that each user can only see his farms, and I don't have to call the Where on each query to the db.
Now, I want to filter all the fruits from one farm, I tried this (in Farm class):
from fruit in Fruits where fruit .... select fruit
but the query generated doesn't include the where clause, which is very important because I have dozens of thousands of rows and is not efficient to load them all and filter them when they're Objects.
I read that lazy loaded properties get filled the first time they're accessed but they read ALL the data, no filters can be applied UNLESS you do something like this:
from fruits in db.Fruits where fruit .... select fruit
But I can't do that, because Farm has no knowledge of DbContext (I don't think it should(?)) but also to me it just loses the whole purpose of using navigation properties if I have to work with all the data and not just the one that belongs to my Farm.
So,
am I doing anything wrong / making wrong assumptions?
Is there any way I can apply a filter to a navigation property that gets generated to the real query? (I'm working with a lot of data)
Thank you for reading!
Unfortunately, I think any approach you might take would have to involve fiddling with the context, not just the entity. As you've seen, you can't filter a navigation property directly, since it's an ICollection<T> and not an IQueryable<T>, so it gets loaded all at once before you have a chance to apply any filters.
One thing you could possibly do is to create an unmapped property in your Farm entity to hold the filtered fruit list:
public class Farm
{
....
public virtual ICollection<Fruit> Fruits { get; set; }
[NotMapped]
public IList<Fruit> FilteredFruits { get; set; }
}
And then, in your context/repository, add a method to load a Farm entity and populate FilteredFruits with the data you want:
public class MyDbContext : DbContext
{
....
public Farm LoadFarmById(int id)
{
Farm farm = this.Farms.Where(f => f.Id == id).Single(); // or whatever
farm.FilteredFruits = this.Entry(farm)
.Collection(f => f.Fruits)
.Query()
.Where(....)
.ToList();
return farm;
}
}
...
var myFarm = myContext.LoadFarmById(1234);
That should populate myFarm.FilteredFruits with only the filtered collection, so you could use it the way you want within your entity. However, I haven't ever tried this approach myself, so there may be pitfalls I'm not thinking of. One major downside is that it would only work with Farms you load using that method, and not with any general LINQ queries you perform on the MyDbContext.Farms dataset.
All that said, I think the fact that you're trying to do this might be a sign that you're putting too much business logic into your entity class, when really it might belong better in a different layer. A lot of the time, it's better to treat entities basically as just receptacles for the contents of a database record, and leave all the filtering/processing to the repository or wherever your business/display logic lives. I'm not sure what kind of application you're working on, so I can't really offer any specific advice, but it's something to think about.
A very common approach if you decide to move things out the Farm entity is to use projection:
var results = (from farm in myContext.Farms
where ....
select new {
Farm = farm,
FilteredFruits = myContext.Fruits.Where(f => f.FarmId == farm.Id && ...).ToList()
}).ToList();
...and then use the generated anonymous objects for whatever you want to do, rather than trying to add extra data to the Farm entities themselves.
Just figured I'd add another solution to this having spent some time trying to append DDD principles to code first models. After searching around for some time I found a solution like the one below which works for me.
public class FruitFarmContext : DbContext
{
public DbSet<Farm> Farms { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Farm>().HasMany(Farm.FruitsExpression).WithMany();
}
}
public class Farm
{
public int Id { get; set; }
protected virtual ICollection<Fruit> Fruits { get; set; }
public static Expression<Func<Farm, ICollection<Fruit>>> FruitsExpression = x => x.Fruits;
public IEnumerable<Fruit> FilteredFruits
{
get
{
//Apply any filter you want here on the fruits collection
return Fruits.Where(x => true);
}
}
}
public class Fruit
{
public int Id { get; set; }
}
The idea is that the farms fruit collection is not directly accessible but is instead exposed through a property that pre-filters it.
The compromise here is the static expression that is required to be able to address the fruit collection when setting up mapping.
I've started to use this approach on a number of projects where I want to control the access to an objects child collections.
Lazy loading doesn't support filtering; use filtered explicit loading instead:
Farm farm = dbContext.Farms.Where(farm => farm.Owner == someOwner).Single();
dbContext.Entry(farm).Collection(farm => farm.Fruits).Query()
.Where(fruit => fruit.IsRipe).Load();
The explicit loading approach requires two round trips to the database, one for the master and one for the detail. If it is important to stick to a single query, use a projection instead:
Farm farm = (
from farm in dbContext.Farms
where farm.Owner == someOwner
select new {
Farm = farm,
Fruit = dbContext.Fruit.Where(fruit => fruit.IsRipe) // Causes Farm.Fruit to be eager loaded
}).Single().Farm;
EF always binds navigation properties to their loaded entities. This means that farm.Fruit will contain the same filtered collection as the Fruit property in the anonymous type. (Just make sure you haven't loaded into the context any Fruit entities that should be filtered out, as described in Use Projections and a Repository to Fake a Filtered Eager Load.)

Categories