Unity Container - 将动态 DbContext 依赖注入到服务中

Unity Container - Dependency Injection of Dynamic DbContext into Service

我正在构建一个 .Net Web API,它使用带有 Entity Framework 的服务+存储库模式。每个控制器的 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 的实例注入到服务构造函数中。给定 SomeContext 的生命周期应该是 API 请求的持续时间。困难在于 SomeContext 的连接字符串是动态的,并且在作为 API 请求的一部分提供运行时参数 'client' 之前无法得知。

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

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

ContextFactory.GetClientContext(client);

如何以及在哪里配置 Unity 容器来管理这个动态 SomeContext?

补充说明:

感谢您的帮助!

我认为您不应该将上下文作为依赖项传递,上下文应该尽可能短,每次使用后都将其丢弃。这是我的 IDataService 中的一些示例(在我的其他 services/facades 中使用 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;
        }
    }

将运行时参数合并到 Unity 依赖注入对象中的技巧是 InjectionFactory:

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

InjectionFactory lets you specify a factory method the container will use to create the object

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

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

    return ContextFactory.GetClientContext(client);
}

因此,Unity 会将 ISomeContext 类型解析为由 GetClientContext() return编辑的 SomeContext。

RegisterType() 的 PerRequestLifetimeManager() 参数指示 Unity 在单个 HTTP 请求的生命周期内使用 returned SomeContext 实例。为了让 Unity 自动处理实例,您还必须 register the UnityPerRequestHttpModule in UnityMvcActivator.cs:

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

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

我已经使用 Unity Container 在 Repository/Service 中获取动态 DbContext。以下代码对我有用。按照这些步骤

  1. IMyDbContext.cs

    public 接口 IMyDbContext: IDisposable
    {
    DbContextConfiguration 配置 { get; }
    数据库数据库{ get; }
    DbSet Set() 其中 T : class;
    DbSet Set(类型类型);
    DbEntityEntry 条目(T 实体),其中 T:class;
    int SaveChanges();
    对象 MyContext { 得到; }
    }

  2. MyDbContext.cs

    public 部分 class MyDbContext : DbContext, IMyDbContext
    {
    对象 IMyDbContext.MyDbContext
    {
    得到
    {
    return 新 MyDbProject.MyDbContext();
    }
    }
    }

  3. UnityConfig.cs 文件

    // 数据库类型注册
    container.RegisterType();
    // Repository/Services 类型注册
    container.RegisterType();

  4. MyRepository.cs

    public class 我的资料库:我的资料库
    {
    IMyDbContext dbContext;
    MyDbContext myContext;
    public我的存储库(IMyDbContext _dbContext)
    {
    dbContext = _dbContext;
    myContext = (MyDbProject.MyDbContext)dbContext.MyDbContext;
    }
    public 列表获取()
    {
    return myContext.SP_GetEntity().ToList();
    }
    }

  5. MyController.cs

    私有只读 IMyRepository _repo = null;
    // MyController
    的构造函数 public MyyController(IMyRepository 回购)
    {
    _repo = 回购;
    }

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