I am developing a multi-tenant web application using MVC4 and EF5. I previously asked this question regarding filtering my DbContext: Is it bad practice to filter by ID within the repository pattern.
Apparently, my approach was sound, but it was suggested that rather than handling all filtering in that single repository, I provide a context already filtered to tenant level using 'a repository or DBContextWrapper class that would feed your [my] normal repository'.
Unfortunately I am no MVC expert, so I started implementing this as best I could, but upon researching filtering in EF for other multi-tenant applications I found a question for a very similar case Multi-tenancy web application with filtered dbContext, though completely failed to understand the answer to it.
In my application, the CompanyID is a property of the User class, so should be taken directly from the authenticated user. Eg:
int CompanyID = db.Users.Single(u => u.Email == User.Identity.Name).CompanyID;
My current approach does appear to work, however I'm pretty sure I have gone about it the wrong way and/or have done it inefficiently based on what I've seen in other Questions about doing the same sort of thing. In another question Solutions for a simple multi tenant web application with entity framework reflection is used to do this, but I'm not able to work out whether it would apply in my case, or even how to use it.
I would be extremely appreciative if anyone can explain the best way of going about this, and the pros/cons of differing ways. Thanks :)
My current implementation is as follows:
DB
- One database, multiple tenants.
- All tables link back to Company table one way or another, although not all have a CompanyID field.
TestController.cs
public class TestController : Controller
{
private BookingSystemEntities db = new BookingSystemEntities();
public ActionResult Index()
{
var user = db.Users.Single(u => u.Email == User.Identity.Name);
IBookingSystemRepository rep = new BookingSystemRepository(db, user);
return View(rep.GetAppointments(false));
}
}
BookingSystemRepository.cs
public class BookingSystemRepository : IBookingSystemRepository
{
private CompanyBookingSystemRepository db;
public BookingSystemRepository(BookingSystemEntities context, User user)
{
this.db = new CompanyBookingSystemRepository(context, user);
}
public IEnumerable<Appointment> GetAppointments()
{ return GetAppointments(false); }
public IEnumerable<Appointment> GetAppointments(bool includeDeleted)
{
return includeDeleted
? db.Appointments
: db.Appointments.Where(a => a.Deleted.HasValue);
}
public IEnumerable<Client> GetClients()
{ return GetClients(false); }
public IEnumerable<Client> GetClients(bool includeDeleted)
{
return includeDeleted
? db.Clients
: db.Clients.Where(c => c.Deleted.HasValue);
}
public void Save()
{
db.SaveChanges();
}
public void Dispose()
{
if (db != null)
db.Dispose();
}
}
CompanyBookingSystemRepository.cs
public class CompanyBookingSystemRepository
{
private BookingSystemEntities db;
private User User;
public IEnumerable<Appointment> Appointments { get { return db.Appointments.Where(a => a.User.CompanyID == User.CompanyID).AsEnumerable<Appointment>(); } }
public IEnumerable<Client> Clients { get { return db.Clients.Where(a => a.CompanyID == User.CompanyID).AsEnumerable<Client>(); } }
public CompanyBookingSystemRepository(BookingSystemEntities context, User user)
{
db = context;
this.User = user;
}
public void SaveChanges()
{
db.SaveChanges();
}
public void Dispose()
{
if (db != null)
db.Dispose();
}
}