Reputation: 14907
I have an Entity Framework, code-first based app that I have to make multi-tenant, which is to say that there are about a half-dozen "top level" entities that now need to reference the specific tenant ID. (As we get to 100's of users, no, we're not going to maintain individual schema, so please don't suggest that. :))
With an object-oriented abstraction over the data access like EF, I'm trying to imagine how I can get to a place where I don't need to change any of the underlying code outside of the dbcontext to make this work. Essentially, I want to use these as my success criteria:
It seems like setting the tenant ID could be done with the entity class itself (haven't thought through the injection on that, some kind of shim stuck in there), but I'm not sure how best to go about adding an additional filter for querying.
Upvotes: 2
Views: 967
Reputation: 101483
I'm not sure it can be done completely without code changes, but here's how I would approach this. First, introduce an interface for your multi-tenant entities (I assume each of them has TenantID
property, mapped to database column):
public interface IMultiTenantEntity {
int TenantID { get; set; }
}
Then implement it for all your entities. They are autogenerated, but partial, so just do:
public partial class YourEntity : IMultiTenantEntity {}
Now, to fill this property on saving, override SaveChanges
in your context (again, it's autogenerated, but partial, so you don't have to touch autogenerated code):
public partial class YourContext : DbContext
{
private int _tenantId;
public override int SaveChanges() {
var addedEntities = this.ChangeTracker.Entries().Where(c => c.State == EntityState.Added)
.Select(c => c.Entity).OfType<IMultiTenantEntity>();
foreach (var entity in addedEntities) {
entity.TenantID = _tenantId;
}
return base.SaveChanges();
}
public IQueryable<Code> TenantCodes => this.Codes.Where(c => c.TenantID == _tenantId);
}
Above I assume you already injected current tenant id into _tenantId
field somehow.
Then, for each entity set, add separate property which will return this set filtered by TenantID
(again in partial class for your context):
public IQueryable<YourEntity> TenantYourEntities => this.YourEntities.Where(c => c.TenantID == _tenantId);
Now all you need to do is find all references to the sets of YourEntities
(with right-click > find all references) and replace them with references to TenantYourEntities
. Then all your queries will be filtered by TenantID
without much work. Of course, don't replace references where you use DbSet to modify entities (Db.YourEntities.Add(...)
).
Upvotes: 3
Reputation: 239290
Well, technically, as long as the tenant ID is known at the time of the context instantiation, you can simply set a field on your context with that value, and reference that field in your overloads. For example, you could do something like read it from Application Settings. Right-click on your project and choose "Properties". Then, go to the "Settings" tab, and turn it on. Put in whatever you will use in development there. Then, add configurations to your project for each tenant, and edit the config transforms to switch it out the appropriate value. In your DI initialization, then, you can read this setting value and inject it as a constant.
If the tenant is being set at runtime, such as via part of the URL, then using DI becomes a bit more difficult. The context will generally be request-scoped, so that's not really a problem. However, the DI initialization is usually not done within the request pipeline. At that point, you'd probably just need to set the value manually, or otherwise create your context within code that is part of the request pipeline, such as the controller.
Upvotes: 0