Unity容器 - 将动态DbContext依赖注入服务

时间:2018-06-15 21:05:20

标签: .net asp.net-web-api dependency-injection unity-container

我正在构建一个使用Service + Repository模式w / Entity Framework的.Net Web API。每个控制器的CRUD操作都会中继通过调用服务检索的数据。

我有一个扩展DbContext的SomeContext:

public class SomeContext : DbContext
{
    public SomeContext(string connString) : base(connString) { }

    // DbSets
    ...
}

使用接受ISomeContext的构造函数初始化服务:

public class Service : IService
{
    public Service(ISomeContext ctx) : base(ctx)
    {
        _alpha = new AlphaRepository(ctx);
        ...
    }

    GetAllAlpha()
    {
        return _alpha.Get();
    }
    ...
}

我想使用(Unity容器)依赖注入将SomeContext的实例注入到Service构造函数中。给定SomeContext的生命周期应该是API请求的持续时间。困难在于SomeContext的连接字符串是动态的,并且在作为API请求的一部分提供运行时参数“client”之前无法知道。

此外,由于我的每个租户数据库环境,因此存在不确定数量的客户端,因此存在连接字符串。因此,我不能只根据'client'参数注册 n 已知的SomeContexts和Resolve()。

相反,内部开发的NuGet包与暴露的ContextFactory让我可以为客户端检索适当的SomeContext:

ContextFactory.GetClientContext(client);

如何以及在何处配置Unity Container以管理此动态SomeContext?

附加说明:

  • Unity已经将依赖注入IService服务 我的控制器行动。因此,Service构造函数是 在我创建的任何Web API ActionFilters之前执行。这意味着我无法在注射之前识别出“客户”......我认为这意味着我需要使用工厂和/或委托......?
  • 我已经阅读了将抽象工厂与DbContext的委托,public delegate IDbContext CreateDbContext(string client);和NuGet的适配器结合使用的内容 包的GetClientContext请求,但我无法做到 把它们拼凑成一个可行的解决方案。

感谢您的帮助!

3 个答案:

答案 0 :(得分:1)

我认为你不应该将上下文作为依赖项传递,上下文应该尽可能短,并在每次使用后处理它。以下是我的IDataService中的一些示例(在我的其他服务/外观中使用dep inj注入)

    public DataService(IMapper mapper) : base(mapper) { }

    ...

    public UserDto GetUser(string ADUser)
    {
        Func<UserDto> action = () =>
        {
            return GetUsers(u => u.UserName == ADUser && u.Active == true).SingleOrDefault();
        };

        return ExecutorHandler(action, true);
    }

    public IList<UserDto> GetUsers(bool runSafeMode = true)
    {
        Func<IList<UserDto>> action = () =>
        {
            return GetUsers(_ => true);
        };

        return ExecutorHandler(action, runSafeMode);
    }

    private IList<UserDto> GetUsers(Expression<Func<User, bool>> predicate, bool runSafeMode = true)
    {
        Func<IList<UserDto>> action = () =>
        {
            using (var ymse = YMSEntities.Create())
            {
                var users = ymse.User
                    .Include(u => u.UserUserProfile)
                    .Include(m => m.UserUserProfile.Select(uup => uup.UserProfile))
                    .Include(m => m.UserUserProfile.Select(uup => uup.User))
                    .Include(m => m.UserUserProfile.Select(uup => uup.UserProfile.UserProfileModule))
                    .Where(predicate).OrderBy(u => u.UserName).ToList();

                return MappingEngine.Map<IList<UserDto>>(users);
            }
        };

        return ExecutorHandler(action, runSafeMode);
    }

    protected T ExecutorHandler<T>(Func<T> action, bool runSafeMode)
    {
        if (runSafeMode)
            return SafeExecutor(action);

        return Executor(action);
    }

    protected T SafeExecutor<T>(Func<T> action, int retryCount = 2)
    {
        try
        {
            return action();
        }
        catch (SqlException sqlEx)
        {
            if (retryCount == ConfigService.GetConfig("SQLRetryCount", 1))
            {
                // reached maximum number of retries
                throw;
            }

         ...

我的IDataService有两个实现,一个用于在线模式,一个离线,在Unity中设置如此(工厂决定在启用或不启用wifi时使用哪一个),可能类似于使用不同的连接字符串注入正确的DataService ):

    unityContainer.RegisterType<IDataService, OfflineDataService>("OfflineDataService", new ContainerControlledLifetimeManager(), new InjectionConstructor(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ServiceLocator.Current.GetInstance<IMapper>()));

    unityContainer.RegisterType<IDataService, DataService>(new ContainerControlledLifetimeManager());

我建议在这里放置一个工厂方法,以便在运行时确定正确的客户端(用注入的实体创建工厂替换Entities.Create()):

    using (var ymse = YMSEntities.Create())

至于你对使用交易的评论,在我看来,数据层不应该关心交易,只关心处理许多实体的业务层。以下是我在外观中的操作方法(多个数据服务调用):

    public void SetTrailerWeightFull(Guid idShunter, Guid idTrailer, int? previousWeight, int weight, bool runSafeMode = true)
    {
        Action action = () =>
        {
            DataService.SetTrailerWeightFull(idTrailer, weight, runSafeMode);

            AddTrailerEvent(TrailerEventTypeEnum.SCALE_1, idTrailer, previousWeight, weight, "Weight Full", string.Empty, string.Empty);

            DataService.SetShunterWeightWeightFull(idShunter, idTrailer);
        };

        TransactionExecutor(action);
    }

交易代码全部在一个地方共享:

    protected void TransactionExecutor(Action action, TransactionScopeAsyncFlowOption transactionScopeOption = TransactionScopeAsyncFlowOption.Suppress)
    {
        try
        {
            using (var scope = new TransactionScope(transactionScopeOption))
            {
                action();

                scope.Complete();
            }
        }
        catch (Exception ex)
        {
            LogErrorEvent(ex);

            throw;
        }
    }

答案 1 :(得分:0)

将运行时参数合并到Unity Dependency Injected对象中的技巧是InjectionFactory

container.RegisterType<ISomeContext>(
    new PerRequestLifetimeManager(),
    new InjectionFactory(_ => ContextFactoryAdapter.GetSomeContext(new HttpContextWrapper(HttpContext.Current)))
);
  

InjectionFactory使您可以指定容器将用于创建对象的工厂方法

在这种情况下,我根据提供的HttpContextWrapper参数中可用的数据,指定静态ContextFactoryAdapter.GetSomeContext()方法以返回动态SomeContext:

public static SomeContext GetSomeContext(HttpContextWrapper requestWrapper)
{
    var client = requestWrapper.Request.QueryString["client"];

    return ContextFactory.GetClientContext(client);
}

因此,Unity会将ISomeContext类型解析为GetClientContext()返回的SomeContext。

RegisterType()的PerRequestLifetimeManager()参数指示Unity在单个HTTP请求的生存期内使用返回的SomeContext实例。为了使Unity自动处理该实例,您还必须在UnityMvcActivator.cs中register the UnityPerRequestHttpModule

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

有了这些配置,Unity就能解析适当的SomeContext并通过构造函数注入将此实例提供给Service。

答案 2 :(得分:-1)

我已经使用Unity容器在存储库/服务中获取动态DbContext。以下代码为我工作。请按照以下步骤操作

  1. IMyDbContext.cs

    公共接口IMyDbContext:IDisposable
    {
     DbContextConfiguration配置{ }
     数据库数据库{get; }
     DbSet Set()其中T:class;
     DbSet Set(Type type);
     DbEntityEntry Entry(T实体),其中T:class;
     int SaveChanges();
     对象MyContext {get; }
    }

  2. MyDbContext.cs

    公共局部类MyDbContext:DbContext,IMyDbContext
    {
     对象IMyDbContext.MyDbContext
     {
      得到
      {
       返回新的MyDbProject.MyDbContext();
      }
     }
    }

  3. UnityConfig.cs文件

    //数据库类型注册
    container.RegisterType();
    //存储库/服务类型注册
    container.RegisterType();

  4. MyRepository.cs

    公共类MyRepository:IMyRepository
    {
    IMyDbContext dbContext;
    MyDbContext myContext;
    公共MyRepository(IMyDbContext _dbContext)
    {
    dbContext = _dbContext;
    myContext =(MyDbProject.MyDbContext)dbContext.MyDbContext;
    }
    公共列表Get()
    {
    返回myContext.SP_GetEntity()。ToList();
    }
    }

  5. MyController.cs

    私有只读IMyRepository _repo = null;
    // MyController的构造方法
    公共MyyController(IMyRepository存储库)
    {
    _repo = repo;
    }

_repo通过使用Unity容器的依赖注入实例化。