首页 > 解决方案 > Unity Container - 将动态 DbContext 依赖注入到服务中

问题描述

我正在构建一个.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 Container) Dependency Injection 将 SomeContext 的一个实例注入到 Service 构造函数中。给定 SomeContext 的生命周期应该是 API 请求的持续时间。困难在于 SomeContext 的连接字符串是动态的,只有在作为 API 请求的一部分提供运行时参数“client”时才能知道。

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

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

ContextFactory.GetClientContext(client);

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

补充说明:

谢谢你的帮助!

标签: .netasp.net-web-apidependency-injectionunity-container

解决方案


我认为您不应该将上下文作为依赖项传递,上下文应该尽可能短,在每次使用后将其丢弃。以下是我的 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;
        }
    }

推荐阅读