如何使用Unity容器

时间:2016-07-12 17:21:02

标签: entity-framework dependency-injection unity-container

我在另一个帖子上以不同的方式询问this question。我可能不太清楚解释我想要做什么,所以我没有得到解决我的问题的决议或指导。由于我没有足够的权限来添加或修改我的原始帖子和代码,我必须以略微不同的方式重复这个问题。所以,请不要将此标记为重复的问题。

[编辑]

首先,谢谢大家帮我解决这个问题。尽管你提供了所有帮助,我仍然没有找到一个友好的解决方案。我开始担心我可能不像我想的那样聪明聪明。否则,这不应该是一个难以解决的问题。我可能不会问正确的问题。所以,我将以另一种方式提出我的问题。

以下是我的界面:

public interface IDataContext : IDisposable
{
    int SaveChanges();
}

public interface IDataContextAsync : IDataContext
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
    Task<int> SaveChangesAsync();
}


public class DataContext : DbContext, IDataContextAsync
{
    bool _disposed;

    public DataContext(string nameOrConnectionString) : base(nameOrConnectionString)
    {
    }
}

public interface IUnitOfWork : IDisposable
{
    IDataContext DataContext { get; }
    int SaveChanges();
    void Dispose( bool disposing );
    IRepository<TEntity> Repository<TEntity>() where TEntity : class, IObjectState;
    void BeginTransaction( IsolationLevel isolationLevel = IsolationLevel.Unspecified );
    bool Commit();
    void Rollback();
}

public interface IUnitOfWorkAsync : IUnitOfWork
{
    Task<int> SaveChangesAsync();
    Task<int> SaveChangesAsync( CancellationToken cancellationToken );
    IRepositoryAsync<TEntity> RepositoryAsync<TEntity>() where TEntity : class, IObjectState;
}

现在,我的dbcontexts:

public partial class MasterDB : DataContext
{
    public MasterDB( string nameOrConnectionString ) : base( nameOrConnectionString )
    {
    }
}

public partial class SubscriberDB : DataContext
{
    public SubsDB(string nameOrConnectionString) : base(nameOrConnectionString)
    {
    }
}

这是我的工作单位:

public class UnitOfWork : IUnitOfWorkAsync
{
    private IDataContextAsync dataContext;
    private bool disposed;
    private Dictionary<string, dynamic> repositories;

    public UnitOfWork( IDataContextAsync dataContext )
    {
        this.dataContext = dataContext;
        repositories = new Dictionary<string, dynamic>();
    }
    public int SaveChanges()
    {
        return dataContext.SaveChanges();
    }
    // rest of the code omitted for brevity!....
}

这是我的app bootstrapper初始化程序,在app start时调用。为简洁起见,我删除了其他注册。

public class PCUnityBootstrapper : UnityBootstrapper
{
    public IUnityContainer RegisterAllDependencies( IUnityContainer container )
    {
        container.RegisterType<IUnitOfWorkAsync, UnitOfWork>( nameof( DBNames.MasterUOW ) );
        container.RegisterType<IUnitOfWorkAsync, UnitOfWork>( nameof( DBNames.SubsUOW ) );
        container.RegisterType<IConnectionStringProvider, ConnectionStringProvider>();
        container.RegisterType( typeof( IRepositoryAsync<> ), typeof( Repository<> ) );
        container.RegisterType<IDataContextAsync, MasterDB>( nameof( DBNames.MasterDB ), new ContainerControlledLifetimeManager(), new InjectionConstructor( "name=" + nameof( DBNames.MasterDB ) ) );
        container.RegisterType<IDataContextAsync, SubsDB>( nameof( DBNames.SubsDB ), new ContainerControlledLifetimeManager(), new InjectionConstructor( "name=" + nameof( DBNames.SubsDB ) ) );
        return container;
    }

    public IDataContextAsync RegisterDBatRunTime( IUnityContainer container, string namedRegistration,
                    string dataSource, string dbName, string userId = null, string password = null )
    {
        var connString = ( new ConnectionStringProvider() ).GetConnectionString( dataSource, dbName, userId, password );
        container.RegisterType<DataContext>( namedRegistration,
                  new InjectionConstructor( "nameOrConnectionString=" + connString ) );
        return container.Resolve<IDataContextAsync>( namedRegistration );
    }
}
  • 我有一个从DataContext派生的MasterDB DbContext,用于 应用程序范围的配置以及登录验证和 静态上下文。
  • 我有一个也从DataContext派生的SubsDB DbContext 使用不同的架构。由于业务需求,有 具有SubsDB架构的多个sql数据库;每一个都具体 到登录用户所属的组。

现在,MasterDB可以注册并解决,没有任何问题,因为它永远不会改变。问题是注册和解析SubsDB DataContext。

在用户登录之前,基础数据库连接是未知的。因此,即使使用InjectionFactory或任何其他工厂(至少据我所知),我也无法在UnityBootstrapper中的应用程序启动调用中注册此类型,因为数据库名称在连接字符串中是未知的)。

为了满足“尽可能接近组合根”规则,我在我的引导程序中创建了另一种方法 RegisterDBatRunTime 方法。我不能说我喜欢我编写方法的方式。但是当用户完成登录后,我可以调用此方法向正确的数据库注册SubsDB。这里的问题是,我必须在初始化之后保留对我的容器的引用,以便注册SubsDB类型。

所以,这是我的问题(已编辑的)

  1. 我的UnitOfWork有两个命名注册,所以我可以 将正确的DataContext注入其中。但是我该如何注射它们 进入UnitOfWork的构造函数?即使我使用该属性 如下所示,不会在我之间产生紧密耦合 UnitOfWork和Unity容器?

    public UnitOfWork([Dependency(nameof(DBNames.SubsUOW))] IDataContextAsync dataContext) { this.dataContext = dataContext; repositories = new Dictionary(); }

  2. 如果我采取这种方式,我肯定可以检查当前的人 登录用户然后更改dataContext的数据库。 但这会有效吗?

  3. 即使它确实有效,我也不需要另一个的精确复制品 UnitOfWork带我的 DBNames.MasterDB 来处理我的其他人 架构?这与DRY原则背道而驰。

  4. 我一直在努力寻找最后一整周的解决方案,但没有到达任何地方。我不得不说,这里有不少成员提出了不少解决方案。他们中的大多数似乎是可行的答案。但是当我开始实施它们时,我发现自己反对新的砖墙。

    我对依赖注入和IoC容器相对较新,但作为开发人员已经有很多年了。这就是为什么我开始担心我无法找到这个问题的友好解决方案。我走的越深,我就越困惑。 StackOverflow是我发现使用Unity IoC的一些代码示例的少数几个地方之一。其他地方,我只能找到在我独特的情况下似乎没有教育我的样板代码。或者说我的问题毕竟是独特的吗?

    我想要的只是:以正确的方式实现DI并避免我的类和组件之间的紧密耦合。使用运行时信息解析我的一些实例,而不会滥用IoC容器作为服务定位器。

    这几天我疯了。所以,有人请帮忙。我是一个视觉人。因此,实现我独特场景的一些代码将大大减轻我的痛苦。感谢。

1 个答案:

答案 0 :(得分:0)

我编辑了我的答案。不确定它是否是正确的解决方案,但这里有:)

<强> InjectionFactory

// singleton class, but let Unity handle that by using ContainerControlledLifetimeManager
public class DatabaseNameProvider
{
    public string Name { get; set; }
}

public class LoginViewModel
{
    private DatabaseNameProvider databaseNameProvider;
    public LoginViewModel(DatabaseNameProvider databaseNameProvider)
    {
        this.databaseNameProvider = databaseNameProvider;
    }

    private void Login(string username, string password)
    {
         // do login logic
         databaseNameProvider.Name = // insert database name resolution logic
    }
}
public static IUnityContainer InitializeContainer(IUnityContainer container )
{
    container.RegisterType<DatabaseNameProdivder>(new ContainerControllerLifetimeManager()); // singleton
    container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
    container.RegisterType<IDataContextAsync, DataContext>( 
                 new InjectionFactory(container => 
                 {
                     var databaseName = c.Resolve<DatabaseNameProvider>().Name;
                     return new DataContext(databaseName);
                 }));
    container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
    return container;
}

您登录ViewModel将依赖于DatabaseNameProdiver并在登录后应用该名称。

这当然只有在登录完成后创建DataContext才有效。

工厂模式

public class DbContextFactory
{
    private DatabaseNameProvider  provider;
    public DbContextFactory(DatabaseNameProvider provider)
    {
         this.provider = provider;
    }

    public DbContext Create()
    {
         return new DbContext(provider.Name);
    }
}

public class MasterDb : DataContext
{
    public MasterDb(DatabaseNameProvider provider) : base(provider.Name)
    {

    }
}

public static IUnityContainer InitializeContainer(IUnityContainer container )
{
      container.RegisterType<DatabaseNameProdivder>(new ContainerControllerLifetimeManager()); // singleton
      container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
      container.RegisterType<IDbDataContextFactory, DbDataContextFactory>();
      container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();
      return container;
 }

认为最好的方法是工厂模式。这样,数据上下文将在您需要时构建,并且您知道需要访问哪个数据库。

另一种解决方案是在访问数据时创建if-else语句。

另一个注意事项是,逻辑依赖于或在IoC中配置是一种糟糕的模式。