65.9K
CodeProject 正在变化。 阅读更多。
Home

MVC 中的多租户系统,具有单独的数据库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (25投票s)

2014 年 11 月 29 日

CPOL

5分钟阅读

viewsIcon

118514

downloadIcon

69

使用 ASP.NET MVC 创建一个多租户系统,其中每个租户的数据都存储在单独的数据库中

引言

随着宽带和 Web 技术的进步,我们正从传统的桌面应用程序转向基于 Web 的系统。如今,云计算非常流行。Sage 和 Quickbooks 等会计软件包正被 Kashflow 和 Wave Apps 等在线替代品所取代。

Kashflow 和 Wave Apps 等公司并没有为每个客户创建一个独特的软件实例,而是将其系统开发为多租户应用程序——所有用户都使用一个软件实例。在每种情况下,它们的数据都通过系统的架构与其他客户的数据分开。

引用

多租户是指软件架构中的一种原则,即软件的一个实例运行在服务器上,为多个租户提供服务。租户是指共享他们所用软件相同视图的用户组。- 维基百科

这可以通过两种方式实现

单一数据库——创建一个单一数据库,并将所有数据存储在此处。每个记录都分配一个租户键,并且只能访问属于该租户的数据。应用程序软件会限制访问。

多个数据库——或者,可以使用一个单独的数据库来存储每个客户的数据。然后可以使用 SQL 登录凭据来限制对数据库的访问。

虽然我多次使用单一数据库方法,但在开始一个新项目时,很明显多个数据库方法可能更合适。

多数据库方法的优势

多数据库方法的主要优点之一是它能够单独备份和还原单个用户的数据。对于单一数据库方法,还原数据库将清除所有客户的更改,并且在单个客户出错时无法提供回滚功能。

此外,如果网站变得非常成功,多数据库系统可以轻松地在单个基础上在服务器之间移动数据。

然而,对我而言,主要的卖点是预计许多客户可能需要对系统进行超出多租户设计所能实现的定制。通过使用单独的数据库,这些可以在需要时迁移到新服务器,并在需要时进行完全定制。虽然这可能会破坏多租户系统的最初优势,但它确实提供了单一数据库系统所不具备的灵活性和未来保障。

多数据库系统的架构

Architecture of a Multi-Tenancy Application

在多数据库多租户系统中,每个用户的数 据都存储在其自己的数据库中。因此,需要一个单独的数据库来保存登录详细信息,并提供用户数据存储位置的详细信息。这可以指向同一台服务器上的数据库,或者远程数据位置。

如何使用 MVC 6 创建多数据库系统

在 Visual Studio 中,创建一个新的 ASP.NET Web 应用程序。

Create a new project

选择 MVC 作为模板类型,并在“更改身份验证”中,确保选择了“个人用户帐户”。在本示例中,我们将使用表单身份验证。

Select MVC as the type

Ensure Individual User Accounts is selected

首先,创建一个名为 _AccountDAL_ 的文件夹——我们将使用它来存储访问帐户数据存储的所有代码。

Create a folder for the account DAL

创建一个新类,并将其命名为 _DataContext.cs_。添加以下代码

public class DataContext : DbContext
{
    public DataContext() : base("accountContext")
    {
    }

    public DbSet<Account> Accounts { get; set; }
    public DbSet<User> Users { get; set; }
}

我们将使用 Entity Framework,Code First,来生成一个 _DataContext_,它代表存储在 _Account_ 数据库中的数据。将会有两个表

  • Accounts - 一个帐户代表一个租户。此数据将包含租户数据存储的位置。每个帐户可以有多个用户。
  • Users - 包含系统所有用户的登录用户名和密码。每个用户都与一个帐户相关联。

向 _web.config_ 添加一个连接字符串以连接到帐户数据库

<connectionStrings>
    <add name="accountContext"
          providerName="System.Data.SqlClient"
          connectionString="Server=desktop\SERVER2012; Database=Accounts;
          Integrated Security=SSPI" />
  </connectionStrings>

同时在 _web.config_ 中,我们还将检查 auth 模式是否设置为表单身份验证

<authentication mode="Forms">
    <forms loginUrl="/Account/Login" cookieless="UseCookies" />
</authentication>

接下来,让我们创建两个类来表示数据库中的表,_User.cs_ 和 _Account.cs_

public class User
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string Name { get; set; }
    public int AccountId { get; set; }
    public virtual Account Account { get; set; }
}

public class Account
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Database { get; set; }
    public virtual ICollection<User> Users { get; set; }
}

现在我们需要创建数据库。创建一个名为 _Accounts_ 的新数据库,并添加两个名为 _Users_ 和 _Accounts_ 的表,如下所示

财务:

用户:

向每个表中添加一些测试数据

最后,让我们对 _Login_ 和 _Logout_ 函数进行一些更改,以使用 _FormsAuthentication_

public ActionResult Login(LoginViewModel model, string returnUrl)
{
   if (ModelState.IsValid)
   {
      var dataContext = new AccountDAL.DataContext();
      var user = dataContext.Users.FirstOrDefault
                 (x => x.Email == model.UserName && x.Password == model.Password);

      if (user != null)
      {
         FormsAuthentication.SetAuthCookie(model.UserName, false);
         return RedirectToLocal(returnUrl);
      }
      else
      {
         ModelState.AddModelError("", "Invalid username or password.");
      }
   }

   // If we got this far, something failed, redisplay form
   return View(model);
}

上面的代码将创建我们 _Account DataContext_ 的新实例,并检查用户名和密码是否与现有用户匹配。如果匹配,我们将设置一个 auth cookie,这将登录用户。

以及 _logout_

public ActionResult LogOff()
{
   FormsAuthentication.SignOut();
   Session.Abandon();
}

上面的代码将清除我们之前设置的 auth cookie。这将有效地将用户注销系统。

如果我们现在运行该项目,我们将能够登录到我们在测试数据中创建的两个公司中的任何一个。一切都非常简单。

现在是多数据库方法了。

让我们为我们的每个公司创建两个新数据库。按照我们在测试数据中“_Account_”表中指定的名称,将它们命名为“_Company1_”和“_Company2_”。在每个数据库中,创建一个名为 _Jobs_ 的新表,如下所示

在每个数据库中添加几个测试作业

公司 1 测试数据

公司 2 测试数据

现在,回到 Visual Studio,创建一个名为 _SystemDAL_ 的文件夹来存储我们所有与实际系统相关的数据对象。

首先,创建一个名为 _DataContext.cs_ 的新类:

public class DataContext : DbContext
{
   public DataContext(string database)
     : base("Data Source=desktop\\Server2012;Initial Catalog=" + database + ";Integrated Security=True")
   {
   }
   
   public DbSet<Job> Jobs { get; set; }
}

这里我们实现了多数据库逻辑。我们不会将连接字符串的名称传递给 _DataContext_ 基构造函数,而是会使用传递给 _DataContext_ 构造函数的数据库名称来构建自己的连接字符串。这将从我们数据库的 _Account_ 表中获取。

创建一个第二个类来表示一个 _job_ 对象

public class Job
{
   public string JobName { get; set; }
   public int Id { get; set; }
}

我们将修改 _Home\Index()_ 函数来加载当前用户的数据

[Authorize]
public ActionResult Index()
{
   // get the current user:
   var accountContext = new AccountDAL.DataContext();
   var user = accountContext.Users.FirstOrDefault(x => x.Email == User.Identity.Name);

   if (user != null)
   {
      // now we have the current user, we can use their Account 
      // to create a new DataContext to access system data:
      var systemContext = new SystemDAL.DataContext(user.Account.Database);
      return View(systemContext.Jobs);
   }
   return View();
}

上面的代码首先创建一个 _Account DataContext_ 的实例,并获取一个代表当前登录用户的对象。然后,我们可以创建一个 _System DataContext_ 实例,将我们想要连接的数据库名称传递给它。

连接后,我们可以将所有公司作业的列表传递给 _View_。

修改 _Index_ 视图如下,替换现有代码

@model IQueryable<MultiTenancy.SystemDAL.Job>

@{
    ViewBag.Title = "Home Page";
}

<br/>
<ul>
    @if (Model != null)
    {
        foreach (var job in Model)
         {
             <li>@job.JobName</li>
         }
    }
</ul>

好了——一个多租户 Web 应用程序,它将每个用户的数 据存储在单独的数据库中!

© . All rights reserved.