使用通用存储库和 IoC 在运行时更改数据库

Change DB in the runtime using Generic Repository and IoC

我正在开发一个 N 层 Web 应用程序,它为每个客户端使用一个数据库,带有用于 IoC 的简单注入器和通用存储库模式。

当我尝试更改数据库连接时,我发现注入器在 Glopal.asax 中的 Application_Start 中运行,我仍然无法弄清楚是谁是当前用户设置连接字符串,我需要在运行时更改它而不是在Application_Start,有什么想法吗?


我的存储库示例 Class

public class Repository<T> : IRepository<T> where T : BaseEntity
{
    private readonly IDBContext _context;
    private IDbSet<T> _entities;

    public Repository(IDBContext context)
    {
        _context = context;
    }

    private IDbSet<T> Entities
    {
        get
        {
            if (_entities == null)
            {
                _entities = _context.Set<T>();
            }
            return _entities;
        }
    }
}

DBContxt 示例 Class

public class DBContext: DbContext, IDBContext
{
    public DBContext()
        : base("Name=DbConnectionString")
    {
        Database.SetInitializer<DBContext>(null);
    }

    //public DBContext(ConnectionStr connection)
    //    : base(connection.conn)
    //{
    //    Database.SetInitializer<DBContext>(null);
    //}

    public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
    {
        return base.Set<TEntity>();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
            .Where(type => !String.IsNullOrEmpty(type.Namespace))
            .Where(
                type =>
                    type.BaseType != null && type.BaseType.IsGenericType &&
                    type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
        foreach (var type in typesToRegister)
        {
            dynamic configurationInstance = Activator.CreateInstance(type);
            modelBuilder.Configurations.Add(configurationInstance);
        }
        base.OnModelCreating(modelBuilder);
    }
}

您将 运行 遇到一个问题 - 您如何知道用户对他们选择的任何数据库都是有效的?

他们是否 select 从下拉列表中输入数据库,然后输入凭据? (可能是个坏主意)你有一个主数据库有用户名 // 密码 // 数据库映射吗?

一旦你弄明白了,一种方法是重写 web.config http://www.beansoftware.com/ASP.NET-Tutorials/Modify-Web.Config-Run-Time.aspx 中的连接字符串。我不太喜欢这个主意。

更好的方法是在创建数据库上下文时传递连接字符串:Pass connection string to code-first DbContext

首先,你会遇到一个严重的问题,因为要做到这一点,你必须知道每个用户的密码,或者以某种方式使其明了。我不确定是否可以完成。如果可以的话,下面的代码会给你一个很好的起点。

如果您仍想这样做,则需要一个 UserContext 服务,该服务 returns 来自 HttpContext 的当前用户。并以此构建您的连接字符串。

这可能或应该看起来像这样:

public interface IUserContext
{
    string CurrentUser { get; } 
}

public class HttpUserContext : IUserContext
{
    public string CurrentUser
    {
        get { return HttpContext.Current.User.Identity.Name; }
    }
}

public class ConnectionStringFactory
{
    private readonly IUserContext userContext;
    private readonly string partialConnectionString;

    public ConnectionStringFactory(
        IUserContext userContext, 
        string partialConnectionString)
    {
        this.userContext = userContext;
        this.partialConnectionString = partialConnectionString;
    }

    public string GetConnectionString()
    {
            var builder = new SqlConnectionStringBuilder(partialConnectionString);
            builder.UserID = this.userContext.CurrentUser;
            return builder.ConnectionString;
    }
}

//register like this
var connectionStringFactory = new ConnectionStringFactory(
         new HttpUserContext(), 
         "yourconnectionString");
container.RegisterSingle(connectionStringFactory);

container.RegisterPerWebRequest<YourDbContext>(() =>
{
    var connectionFactory = container.GetInstance<ConnectionStringFactory>();
    var entityConnectionString = connectionFactory.GetConnectionString;
    return new YourDbContext(entityConnectionString);
});

注意:对于 Entity Framework,您可能需要调整 ConnectionFactory。

更好的解决方案是使用 SQL 服务器的集成安全性,让您的 AppPool 模拟当前用户。

更好的方法是使用单个用户帐户(AppPool 身份)连接到您的数据库,并通过向模型添加一些额外的表并添加类似 IAuthorizedRepository 的内容来控制应用程序中的身份验证在您的 IRepository 界面之上。看看围绕这个主题的讨论:How to implement row based security