是否有另一种在Autofac中更改数据库实例的方法

时间:2014-06-12 15:16:31

标签: c# entity-framework-4 autofac

我有一个使用多个数据库的应用程序。

我发现我可以通过使用连接构建器来改变它。像这样:

var configNameEf = "ProjectConnection";
var cs = System.Configuration.ConfigurationManager.ConnectionStrings[configNameEf].ConnectionString;
var sqlcnxstringbuilder = new SqlConnectionStringBuilder(cs);
sqlcnxstringbuilder.InitialCatalog = _Database;

然后我需要更改UnitOfWork的autofac Lifescope,以便它现在将请求重定向到良好的数据库实例。

我在很长一段时间后发现的是,我可以从DelegatedHandler那样做:

HttpConfiguration config = GlobalConfiguration.Configuration;
DependencyConfig.Register(config, sqlcnxstringbuilder.ToString());
request.Properties["MS_DependencyScope"] = config.DependencyResolver.GetRequestLifetimeScope();

问题是,还有其他方法可以改变请求的MS_DependencyScope参数。这个解决方案有效,但我觉得它有点阴暗。

这是DependencyConfig中的注册表:

public static void Register(HttpConfiguration config, String bdContext = null)
        {
        var builder = new ContainerBuilder();
        builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
        builder.Register(_ => new ProjectContext(bdContext)).As<ProjectContext>().InstancePerApiRequest();

        builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerApiRequest();

        // Register IMappingEngine
        builder.Register(_ => Mapper.Engine).As<IMappingEngine>().SingleInstance();
        config.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build());
        config.DependencyResolver.BeginScope();

    }

1 个答案:

答案 0 :(得分:2)

描述问题的方式以及我的评论的答案听起来的方式,您有以下情况:

  • 该应用程序使用每个请求的终身工作单元。我从您的注册中看到了这一点。
  • 在给定时间点,应用程序中只使用一个数据库。也就是说,每个请求都不必确定不同的数据库;它们都使用相同的一个,直到连接字符串发生变化。从使用固定应用程序设置检索数据库的方式可以看出这一点。
  • 配置中的连接字符串可能会更改,此时需要更改所使用的数据库。

假设我已正确理解这个问题......

如果应用程序设置在web.config中(如图所示),则更改web.config中的字符串实际上会重新启动应用程序。此问题更详细地讨论了这个问题: How to prevent an ASP.NET application restarting when the web.config is modified?

如果是这样的话,你就没有任何工作要做 - 只需将数据库注册为单例,当web.config发生变化时,应用程序重新启动,重新运行应用启动逻辑,获取新数据库,并发生魔术。

如果应用设置不在web.config中,那么您应该创建一个项目上下文工厂类。

工厂将作为读取配置和构建与数据库连接的逻辑的封装。它还可以作为在设置未发生变化时缓存连接的地方。

界面看起来像这样:

public interface IProjectContextFactory
{
  ProjectContext GetContext();
}

一个简单的实现(没有锁定,错误处理,日志记录以及你应该放入的所有好东西)可能是:

public class ProjectContextFactory : IProjectContextFactory
{
  private ProjectContext _currentContext = null;
  private string _currentConnectionString = null;
  private const string ConnectionKey = "ProjectConnection";

  public ProjectContext GetContext()
  {
    // Seriously, don't forget the locking, etc. in here
    // to make this thread-safe! I'm omitting it for simplicity.
    var cs = ConfigurationManager.ConnectionStrings[ConnectionKey].ConnectionString;
    if(this._currentConnectionString != cs)
    {
      this._currentConnectionString = cs;
      var builder = new SqlConnectionStringBuilder(cs);
      builder.InitialCatalog = _Database;
      this._currentContext = new ProjectContext(builder.ToString());
    }
    return this._currentContext;
  }
}

好的,现在您有一个工厂可以缓存已构建的项目上下文,并且仅在配置更改时才更改它。 (如果您没有缓存ProjectContext而是缓存数据库连接字符串或其他内容,原则仍然存在 - 您需要一个管理缓存和检查配置的类,以便更改可以根据需要发生。)

现在你有了一个缓存/工厂,你可以在Autofac注册中使用它而不是原始连接字符串。

builder.RegisterType<ProjectContextFactory>()
       .As<IProjectContextFactory>()
       .SingleInstance();
builder.Register(c => c.Resolve<IProjectContextFactory>().GetContext())
       .As<ProjectContext>()
       .InstancePerRequest();

当配置的连接字符串发生更改时,ProjectContext现在将根据请求进行更改。

除此之外:我发现请求生命周期范围内存在奇怪的事情。我在您的注册中看到您正在创建自己的请求生命周期范围。使用这种方法你不应该这样做。但是,如果您发现仍然需要(或想要),则需要确保原始创建的生命周期范围和您创建的范围都已处理完毕。生命周期范围不会自动生成处置并挂在物件参考上,以便处理。很有可能如果你没有正确处理这个问题,那么你就会有一个微妙的内存泄漏。 Autofac Web API集成将负责为您创建和处理请求生命周期,但如果您更改请求生命周期,则会发生奇怪的事情。