Я работаю над созданием мультитенантного веб-приложения (API), которое будет обращаться к различным базам данных в зависимости от пользователя, который входит в систему. Будет одна база данных для управления входами пользователей, которая содержит информацию о сервере и базе данных, необходимую для созданиясторона кода строки подключения для построения DbContext для конкретного пользователя, который вошел в систему. Вот проводка вверх от самого низкого уровня к контроллеру:
GenericRepository:
public class GenericRepository<TEntity> : GenericRepositoryBase<TEntity> where TEntity : class
{
public GenericRepository(DbContext context) : base(context)
{
//GenericRepositoryBase has the CRUD operations
}
}
IRepository:
public interface IRepository<TEntity> where TEntity : class
{
IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
Task<TEntity> GetByID(object id);
Task<ContextReturn> Insert(TEntity entity);
Task<ContextReturn> Update(TEntity entityToDelete);
Task<ContextReturn> Remove(TEntity entityToUpdate);
}
Репозиторий конкретной модели:
public class EmployeeRepository : GenericRepository<Employee>, IEmployeeRepository
{
public EmployeeRepository(DbContext context) : base(context)
{
}
}
Файл Startup.cs в API содержит:
services.AddScoped<IEmployeeRepository, EmployeeRepository>();
и, наконец, подключение контроллера:
public class EmployeeController : ControllerBase
{
IEmployeeRepository EmployeeRepository { get; }
public EmployeeController(
IEmployeeRepository employeeRepository)
{
EmployeeRepository = employeeRepository;
}
public IActionResult GetEmployees()
{
var data = EmployeeRepository.GetAll().ToList();
return Ok(data);
}
}
Все это прекрасно работает при работе с одним DbContext, объявленным при запуске как:
services.AddDbContextPool<DbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SystemManagementConnection"), sqlServerOptions => sqlServerOptions.CommandTimeout(300)));
(На самом деле я использую этот точный контекст DI с соответствующим именем для доступа к базе данных управления пользователями).
Проблема возникает при попытке динамически создать DbContext на основе входа пользователя из-за парыиз причин:
При использовании DI таким способом вы не создаете DbContext в контроллере и не передаете его в хранилище, так как я использую интерфейсы для доступа к ним.DI использует DbContext, объявленный при запуске, чтобы пройти через него, который я не хочу для всех репозиториев.
Startup.cs не знает никакой пользовательской информации, поэтому его нельзя передать никаким методом для создания DbContext на основе этой информации.
Я попытался разрешить первый, используя следующее:
services.AddScoped<IEmployeeRepository, EmployeeRepository>((ctx) => { DbContext context = DbContextFactory.Create(); return new EmployeeRepository(context); });
, привязанный к:
public class DbContextFactory
{
public static DbContext Create()
{
var conn = GetConnectionString();
if (!string.IsNullOrEmpty(conn))
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseSqlServer(conn);
return new DbContext(optionsBuilder.Options);
}
else
{
throw new ArgumentNullException("ConnectionId");
}
}
public static string GetConnectionString()
{
var data = "";
return data;
}
}
, а затем передал IHttpContextAccessor в конструктор для доступа кпользователь (я уверен, что вы уже знаете, куда это идет ...), но, конечно, это требует, чтобы класс не был статическим, что затем выдает ошибки в файле Startup.cs.По сути, это не решает мою проблему.
Единственное другое решение, которое я могу придумать, - это объявить ALL моих DbContexts в сервисах файла Startup.cs, но опять же,мои репозитории все еще не будут знать, какой из них использовать, основываясь на пользователе.
Конечно, я не первый, кто сталкивается с этим.Если кто-то может помочь, я был бы очень признателен.