更新(2021 年 9 月):4 年后我制作了:
原答案:
我试图通过编辑他的帖子让@AyendeRahien 参与讨论技术实现的尝试没有成功:),所以下面我将从上面解决我的担忧:
1.多租户数据库与多个数据库
这里有一些关于多租户的Ayende's thoughts。
在我看来问题归结为
简单来说,在有大量记录的几个租户的情况下,将租户信息添加到索引中将不必要地增加索引大小,并且处理租户 ID 会带来一些您希望避免的开销,所以去吧那么对于两个数据库。
2。多租户数据库的设计
第 1 步。将TenantId 属性添加到要支持多租户的所有持久文档中。
/// <summary>
/// Interface for top-level entities, which belong to a tenant
/// </summary>
public interface ITenantedEntity
{
/// <summary>
/// ID of a tenant
/// </summary>
string TenantId { get; set; }
}
/// <summary>
/// Contact information [Tenanted document]
/// </summary>
public class Contact : ITenantedEntity
{
public string Id { get; set; }
public string TenantId { get; set; }
public string Name { get; set; }
}
第 2 步。为 Raven 的 session(IDocumentSession 或 IAsyncDocumentSession)实施 facade 以处理多租户实体。
示例代码如下:
/// <summary>
/// Facade for the Raven's IAsyncDocumentSession interface to take care of multi-tenanted entities
/// </summary>
public class RavenTenantedSession : IAsyncDocumentSession
{
private readonly IAsyncDocumentSession _dbSession;
private readonly string _currentTenantId;
public IAsyncAdvancedSessionOperations Advanced => _dbSession.Advanced;
public RavenTenantedSession(IAsyncDocumentSession dbSession, ICurrentTenantIdResolver tenantResolver)
{
_dbSession = dbSession;
_currentTenantId = tenantResolver.GetCurrentTenantId();
}
public void Delete<T>(T entity)
{
if (entity is ITenantedEntity tenantedEntity && tenantedEntity.TenantId != _currentTenantId)
throw new ArgumentException("Attempt to delete a record for another tenant");
_dbSession.Delete(entity);
}
public void Delete(string id)
{
throw new NotImplementedException("Deleting by ID hasn't been implemented");
}
#region SaveChanges & StoreAsync---------------------------------------
public Task SaveChangesAsync(CancellationToken token = new CancellationToken()) => _dbSession.SaveChangesAsync(token);
public Task StoreAsync(object entity, CancellationToken token = new CancellationToken())
{
SetTenantIdOnEntity(entity);
return _dbSession.StoreAsync(entity, token);
}
public Task StoreAsync(object entity, string changeVector, string id, CancellationToken token = new CancellationToken())
{
SetTenantIdOnEntity(entity);
return _dbSession.StoreAsync(entity, changeVector, id, token);
}
public Task StoreAsync(object entity, string id, CancellationToken token = new CancellationToken())
{
SetTenantIdOnEntity(entity);
return _dbSession.StoreAsync(entity, id, token);
}
private void SetTenantIdOnEntity(object entity)
{
var tenantedEntity = entity as ITenantedEntity;
if (tenantedEntity != null)
tenantedEntity.TenantId = _currentTenantId;
}
#endregion SaveChanges & StoreAsync------------------------------------
public IAsyncLoaderWithInclude<object> Include(string path)
{
throw new NotImplementedException();
}
public IAsyncLoaderWithInclude<T> Include<T>(Expression<Func<T, string>> path)
{
throw new NotImplementedException();
}
public IAsyncLoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, string>> path)
{
throw new NotImplementedException();
}
public IAsyncLoaderWithInclude<T> Include<T>(Expression<Func<T, IEnumerable<string>>> path)
{
throw new NotImplementedException();
}
public IAsyncLoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, IEnumerable<string>>> path)
{
throw new NotImplementedException();
}
#region LoadAsync -----------------------------------------------------
public async Task<T> LoadAsync<T>(string id, CancellationToken token = new CancellationToken())
{
T entity = await _dbSession.LoadAsync<T>(id, token);
if (entity == null
|| entity is ITenantedEntity tenantedEntity && tenantedEntity.TenantId == _currentTenantId)
return entity;
throw new ArgumentException("Incorrect ID");
}
public async Task<Dictionary<string, T>> LoadAsync<T>(IEnumerable<string> ids, CancellationToken token = new CancellationToken())
{
Dictionary<string, T> entities = await _dbSession.LoadAsync<T>(ids, token);
if (typeof(T).GetInterfaces().Contains(typeof(ITenantedEntity)))
return entities.Where(e => (e.Value as ITenantedEntity)?.TenantId == _currentTenantId).ToDictionary(i => i.Key, i => i.Value);
return null;
}
#endregion LoadAsync --------------------------------------------------
#region Query ---------------------------------------------------------
public IRavenQueryable<T> Query<T>(string indexName = null, string collectionName = null, bool isMapReduce = false)
{
var query = _dbSession.Query<T>(indexName, collectionName, isMapReduce);
if (typeof(T).GetInterfaces().Contains(typeof(ITenantedEntity)))
return query.Where(r => (r as ITenantedEntity).TenantId == _currentTenantId);
return query;
}
public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
{
var query = _dbSession.Query<T, TIndexCreator>();
var lastArgType = typeof(TIndexCreator).BaseType?.GenericTypeArguments?.LastOrDefault();
if (lastArgType != null && lastArgType.GetInterfaces().Contains(typeof(ITenantedEntity)))
return query.Where(r => (r as ITenantedEntity).TenantId == _currentTenantId);
return query;
}
#endregion Query ------------------------------------------------------
public void Dispose() => _dbSession.Dispose();
}
如果你也需要Include(),上面的代码可能需要一些爱。
我的最终解决方案没有像我之前建议的那样将 listeners 用于 RavenDb v3.x(请参阅 my comment 了解原因)或 events 用于 RavenDb v4(因为很难修改其中的查询)。
当然,如果您编写 patches 的 JavaScript 函数,则必须手动处理多租户。