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?
补充说明:
- Unity 已经依赖注入 IService 服务到每个
我的控制器动作。因此,服务构造函数是
在我创建的任何 Web API ActionFilters 之前执行。这意味着我无法在注入之前识别 'client'...我想这意味着我需要使用工厂 and/or 委托...?
- 我读过有关将抽象工厂与 DbContext 委托、
public delegate IDbContext CreateDbContext(string client);
和 NuGet 适配器结合使用的信息
包的 GetClientContext 请求,但我一直无法
将它们拼凑成一个可行的解决方案。
感谢您的帮助!
我认为您不应该将上下文作为依赖项传递,上下文应该尽可能短,每次使用后都将其丢弃。这是我的 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。以下代码对我有用。按照这些步骤
IMyDbContext.cs
public 接口 IMyDbContext: IDisposable
{
DbContextConfiguration 配置 { get; }
数据库数据库{ get; }
DbSet Set() 其中 T : class;
DbSet Set(类型类型);
DbEntityEntry 条目(T 实体),其中 T:class;
int SaveChanges();
对象 MyContext { 得到; }
}
MyDbContext.cs
public 部分 class MyDbContext : DbContext, IMyDbContext
{
对象 IMyDbContext.MyDbContext
{
得到
{
return 新 MyDbProject.MyDbContext();
}
}
}
UnityConfig.cs 文件
// 数据库类型注册
container.RegisterType();
// Repository/Services 类型注册
container.RegisterType();
MyRepository.cs
public class 我的资料库:我的资料库
{
IMyDbContext dbContext;
MyDbContext myContext;
public我的存储库(IMyDbContext _dbContext)
{
dbContext = _dbContext;
myContext = (MyDbProject.MyDbContext)dbContext.MyDbContext;
}
public 列表获取()
{
return myContext.SP_GetEntity().ToList();
}
}
MyController.cs
私有只读 IMyRepository _repo = null;
// MyController
的构造函数
public MyyController(IMyRepository 回购)
{
_repo = 回购;
}
_repo 是使用 Unity 容器通过依赖注入实例化的。
我正在构建一个 .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?
补充说明:
- Unity 已经依赖注入 IService 服务到每个 我的控制器动作。因此,服务构造函数是 在我创建的任何 Web API ActionFilters 之前执行。这意味着我无法在注入之前识别 'client'...我想这意味着我需要使用工厂 and/or 委托...?
- 我读过有关将抽象工厂与 DbContext 委托、
public delegate IDbContext CreateDbContext(string client);
和 NuGet 适配器结合使用的信息 包的 GetClientContext 请求,但我一直无法 将它们拼凑成一个可行的解决方案。
感谢您的帮助!
我认为您不应该将上下文作为依赖项传递,上下文应该尽可能短,每次使用后都将其丢弃。这是我的 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。以下代码对我有用。按照这些步骤
IMyDbContext.cs
public 接口 IMyDbContext: IDisposable
{
DbContextConfiguration 配置 { get; }
数据库数据库{ get; }
DbSet Set() 其中 T : class;
DbSet Set(类型类型);
DbEntityEntry 条目(T 实体),其中 T:class;
int SaveChanges();
对象 MyContext { 得到; }
}MyDbContext.cs
public 部分 class MyDbContext : DbContext, IMyDbContext
{
对象 IMyDbContext.MyDbContext
{
得到
{
return 新 MyDbProject.MyDbContext();
}
}
}UnityConfig.cs 文件
// 数据库类型注册
container.RegisterType();
// Repository/Services 类型注册
container.RegisterType();MyRepository.cs
public class 我的资料库:我的资料库
{
IMyDbContext dbContext;
MyDbContext myContext;
public我的存储库(IMyDbContext _dbContext)
{
dbContext = _dbContext;
myContext = (MyDbProject.MyDbContext)dbContext.MyDbContext;
}
public 列表获取()
{
return myContext.SP_GetEntity().ToList();
}
}MyController.cs
私有只读 IMyRepository _repo = null;
// MyController
的构造函数 public MyyController(IMyRepository 回购)
{
_repo = 回购;
}
_repo 是使用 Unity 容器通过依赖注入实例化的。