2012-01-26 31 views
7

Tôi đang xây dựng một ứng dụng web nhiều bên thuê để bảo mật, chúng tôi cần có một phiên bản của cơ sở dữ liệu cho mỗi đối tượng thuê. Vì vậy, tôi có một MainDB để xác thực và nhiều ClientDB cho dữ liệu ứng dụng.Multitenancy với Fluent nHibernate và Ninject. Một Cơ sở dữ liệu cho mỗi Người thuê nhà

Tôi đang sử dụng Asp.net MVC với Ninject và Fluent nHibernate. Tôi đã thiết lập SessionFactory/Session/Repositories của mình bằng Ninject và Fluent nHibernate trong mô-đun Ninject ở đầu ứng dụng. Các phiên của tôi là PerRequestScope, cũng như các kho lưu trữ. Vấn đề của tôi là bây giờ tôi cần phải instanciate một phiên bản SessionFactory (SingletonScope) cho mỗi người thuê nhà của tôi bất cứ khi nào một trong số họ kết nối với ứng dụng và tạo ra một phiên mới và kho lưu trữ cần thiết cho mỗi yêu cầu web.theo dõi Tôi đang bối rối như thế nào để làm điều này và sẽ cần một ví dụ cụ thể.

Đây là tình huống.

Ứng dụng bắt đầu: Người dùng TenantX nhập thông tin đăng nhập của mình. SessionFactory của MainDB được tạo và mở một phiên làm việc với MainDB để xác thực người dùng. Sau đó, ứng dụng tạo ra cookie auth.

Người thuê truy cập ứng dụng: Tên người thuê + Chuỗi kết nối được trích xuất từ ​​MainDB và Ninject phải xây dựng một đối tượng thuê nhà cụ thể SessionFactory (SingletonScope) cho người thuê đó. Phần còn lại của yêu cầu web, tất cả các bộ điều khiển yêu cầu một kho lưu trữ sẽ được tiêm với một phiên/kho lưu trữ cụ thể của Người thuê dựa trên SessionFactory của đối tượng thuê đó.

Làm cách nào để thiết lập động đó với Ninject? Ban đầu tôi đã sử dụng trường hợp Đặt tên khi tôi có nhiều cơ sở dữ liệu nhưng bây giờ cơ sở dữ liệu là người thuê cụ thể, tôi bị mất ...

+0

bạn có thể làm cách nào, khi người dùng đăng nhập, để cho phép họ thay đổi mật khẩu của họ? Tôi cho rằng mật khẩu của họ nằm trong "MainDB" chứ không phải là TenantDB, đúng không? –

Trả lời

11

Sau khi nghiên cứu thêm, tôi có thể trả lời tốt hơn.

Trong khi có thể chuyển chuỗi kết nối đến ISession.OpenSession, cách tiếp cận tốt hơn là tạo một tùy chỉnh ConnectionProvider. Phương pháp đơn giản nhất là lấy được từ DriverConnectionProvider và ghi đè lên ConnectionString tài sản:

public class TenantConnectionProvider : DriverConnectionProvider 
{ 
    protected override string ConnectionString 
    { 
     get 
     { 
      // load the tenant connection string 
      return ""; 
     } 
    } 

    public override void Configure(IDictionary<string, string> settings) 
    { 
     ConfigureDriver(settings); 
    } 
} 

Sử dụng FluentNHibernate bạn thiết lập các nhà cung cấp như sau:

var config = Fluently.Configure() 
    .Database(
     MsSqlConfiguration.MsSql2008 
      .Provider<TenantConnectionProvider>() 
    ) 

các ConnectionProvider được đánh giá mỗi khi bạn mở một phiên cho phép bạn kết nối với cơ sở dữ liệu người thuê cụ thể trong ứng dụng của bạn.

Vấn đề với cách tiếp cận trên là SessionFactory được chia sẻ. Đây không thực sự là vấn đề nếu bạn chỉ sử dụng bộ đệm mức đầu tiên (vì nó được gắn với phiên) nhưng nếu bạn quyết định bật bộ nhớ cache cấp thứ hai (gắn với SessionFactory).

Do đó, cách tiếp cận được khuyến nghị là có SessionFactory cho mỗi người thuê nhà (điều này sẽ áp dụng cho các chiến lược lược đồ cho mỗi đối tượng thuê và cơ sở dữ liệu cho từng người thuê).

Một vấn đề khác thường bị bỏ qua là mặc dù bộ nhớ cache cấp thứ hai được gắn với SessionFactory, trong một số trường hợp, không gian bộ nhớ cache được chia sẻ (reference). Điều này có thể được giải quyết bằng cách thiết lập thuộc tính "regionName" của nhà cung cấp.

Dưới đây là triển khai hoạt động của SessionFactory cho mỗi người thuê nhà dựa trên yêu cầu của bạn.

Lớp Tenant chứa các thông tin chúng ta cần phải thiết lập NHibernate cho người thuê:

public class Tenant : IEquatable<Tenant> 
{ 
    public string Name { get; set; } 
    public string ConnectionString { get; set; } 

    public bool Equals(Tenant other) 
    { 
     if (other == null) 
      return false; 

     return other.Name.Equals(Name) && other.ConnectionString.Equals(ConnectionString); 
    } 

    public override bool Equals(object obj) 
    { 
     return Equals(obj as Tenant); 
    } 

    public override int GetHashCode() 
    { 
     return string.Concat(Name, ConnectionString).GetHashCode(); 
    } 
} 

Kể từ khi chúng tôi sẽ lưu trữ một Dictionary<Tenant, ISessionFactory> chúng tôi thực hiện các giao diện IEquatable vì vậy chúng tôi có thể đánh giá các phím thuê.

Quá trình nhận được người thuê nhà hiện tại là trừu tượng như vậy:

public interface ITenantAccessor 
{ 
    Tenant GetCurrentTenant(); 
} 

public class DefaultTenantAccessor : ITenantAccessor 
{ 
    public Tenant GetCurrentTenant() 
    { 
     // your implementation here 

     return null; 
    } 
} 

Cuối cùng NHibernateSessionSource trong đó quản lý phiên:

public interface ISessionSource 
{ 
    ISession CreateSession(); 
} 

public class NHibernateSessionSource : ISessionSource 
{ 
    private Dictionary<Tenant, ISessionFactory> sessionFactories = 
     new Dictionary<Tenant, ISessionFactory>(); 

    private static readonly object factorySyncRoot = new object(); 

    private string defaultConnectionString = 
     @"Server=(local)\sqlexpress;Database=NHibernateMultiTenancy;integrated security=true;"; 

    private readonly ISessionFactory defaultSessionFactory; 
    private readonly ITenantAccessor tenantAccessor; 

    public NHibernateSessionSource(ITenantAccessor tenantAccessor) 
    { 
     if (tenantAccessor == null) 
      throw new ArgumentNullException("tenantAccessor"); 

     this.tenantAccessor = tenantAccessor; 

     lock (factorySyncRoot) 
     { 
      if (defaultSessionFactory != null) return; 

      var configuration = AssembleConfiguration("default", defaultConnectionString); 
      defaultSessionFactory = configuration.BuildSessionFactory(); 
     } 
    } 

    private Configuration AssembleConfiguration(string name, string connectionString) 
    { 
     return Fluently.Configure() 
      .Database(
       MsSqlConfiguration.MsSql2008.ConnectionString(connectionString) 
      ) 
      .Mappings(cfg => 
      { 
       cfg.FluentMappings.AddFromAssemblyOf<NHibernateSessionSource>(); 
      }) 
      .Cache(c => 
       c.UseSecondLevelCache() 
       .ProviderClass<HashtableCacheProvider>() 
       .RegionPrefix(name) 
      ) 
      .ExposeConfiguration(
       c => c.SetProperty(NHibernate.Cfg.Environment.SessionFactoryName, name) 
      ) 
      .BuildConfiguration(); 
    } 

    private ISessionFactory GetSessionFactory(Tenant currentTenant) 
    { 
     ISessionFactory tenantSessionFactory; 

     sessionFactories.TryGetValue(currentTenant, out tenantSessionFactory); 

     if (tenantSessionFactory == null) 
     { 
      var configuration = AssembleConfiguration(currentTenant.Name, currentTenant.ConnectionString); 
      tenantSessionFactory = configuration.BuildSessionFactory(); 

      lock (factorySyncRoot) 
      { 
       sessionFactories.Add(currentTenant, tenantSessionFactory); 
      } 
     } 

     return tenantSessionFactory; 
    } 

    public ISession CreateSession() 
    { 
     var tenant = tenantAccessor.GetCurrentTenant(); 

     if (tenant == null) 
     { 
      return defaultSessionFactory.OpenSession(); 
     } 

     return GetSessionFactory(tenant).OpenSession(); 
    } 
} 

Khi chúng ta tạo một thể hiện của NHibernateSessionSource chúng tôi thiết lập mặc định SessionFactory đến cơ sở dữ liệu "mặc định" của chúng tôi.

Khi CreateSession() được gọi là chúng tôi nhận được ví dụ ISessionFactory. Đây sẽ là nhà máy phiên mặc định (nếu đối tượng thuê hiện tại là null) hoặc nhà máy phiên cụ thể của người thuê nhà. Nhiệm vụ định vị nhà máy phiên cụ thể của người thuê được thực hiện theo phương pháp GetSessionFactory.

Cuối cùng, chúng tôi gọi OpenSession trên ví dụ ISessionFactory mà chúng tôi đã nhận được.

Lưu ý rằng khi tạo nhà máy phiên, chúng tôi đặt tên SessionFactory (cho mục đích gỡ lỗi/lược tả) và tiền tố vùng bộ đệm (vì những lý do được đề cập ở trên).

cụ IoC của chúng tôi (trong trường hợp của tôi StructureMap) dây tất cả mọi thứ lên:

x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>(); 
    x.For<ISession>().HttpContextScoped().Use(ctx => 
     ctx.GetInstance<ISessionSource>().CreateSession()); 
    x.For<ITenantAccessor>().Use<DefaultTenantAccessor>(); 

Đây NHibernateSessionSource được scoped như một singleton và ISession mỗi yêu cầu.

Hy vọng điều này sẽ hữu ích.

+0

Kỹ thuật này có nhược điểm là không sử dụng bộ nhớ cache nHibernate. Mặc dù phức tạp hơn, tôi nghĩ rằng có nhiều nhà máy phiên sẽ thích hợp hơn. – Nick

+0

Trong trường hợp đó, hãy xem liên kết thứ hai tôi đã đăng. –

+0

Mặc dù điều này có thể giải quyết vấn đề bộ nhớ cache nHibernate, yêu cầu của tôi là cho một cơ sở dữ liệu cho mỗi đối tượng thuê, do đó, có một cơ sở dữ liệu với một lược đồ cho mỗi đối tượng thuê sẽ không hoạt động. – Nick

0

Nếu tất cả cơ sở dữ liệu trên cùng một máy, có thể thuộc tính lược đồ của ánh xạ lớp có thể được sử dụng để đặt cơ sở dữ liệu trên cơ sở người thuê trước.

Các vấn đề liên quan