10

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();
    }
}
4

1 回答 1

1

与您提供的其他一些示例相比,我更喜欢您的方法。假设每个租户运行相同的代码库和域,基于登录用户的过滤应该是确保您正确过滤数据的最有效方法。(如果没有,您也可以利用它们进行过滤。)

如果您担心过滤没有 CompanyID 的表的数据库性能,您可以故意对数据库进行非规范化以将该字段包含在这些表中。

您引用的反射方法虽然很优雅,但似乎过于复杂,并且比在您的数据库调用中包含 CompanyID 的开销要多得多(特别是因为在两个实例中都发生了数据库调用)。

编辑(评论后):

至于它的其余部分,您似乎编写了很多不必要的多余代码(至少在上面引用的示例中没有)。我不一定理解您为什么要区分 BookingSystemRepository 和 CompanyBookingSystemRepository ,因为从您的代码看来,前者的存在只是为了将调用传递给后者,后者只是使用 UserID 过滤结果(是否有过这样的情况你不会过滤这些结果?)。

您可以完全消除这两个类(以及您在评论中引用的问题),只需将您的方法更改为:

public class TestController : Controller
{
    private BookingSystemEntities db = new BookingSystemEntities();
    public ActionResult Index()
    {
        var user = db.Users.Single(u => u.Email == User.Identity.Name);
        var appointments = db.Appointments.Where(a => a.User.CompanyID == user.CompanyID).AsEnumerable();
        return View(appointments);
    }

    public override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

从那里开始,如果您担心性能,您真的应该在数据库中进行所有过滤,然后只调用这些程序来返回您的数据。

于 2013-06-19T13:06:51.953 回答