我正在构建一个使用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?
附加说明:
public delegate IDbContext CreateDbContext(string client);
和NuGet的适配器结合使用的内容
包的GetClientContext请求,但我无法做到
把它们拼凑成一个可行的解决方案。感谢您的帮助!
答案 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。以下代码为我工作。请按照以下步骤操作
IMyDbContext.cs
公共接口IMyDbContext:IDisposable
{
DbContextConfiguration配置{ }
数据库数据库{get; }
DbSet Set()其中T:class;
DbSet Set(Type type);
DbEntityEntry Entry(T实体),其中T:class;
int SaveChanges();
对象MyContext {get; }
}
MyDbContext.cs
公共局部类MyDbContext:DbContext,IMyDbContext
{
对象IMyDbContext.MyDbContext
{
得到
{
返回新的MyDbProject.MyDbContext();
}
}
}
UnityConfig.cs文件
//数据库类型注册
container.RegisterType();
//存储库/服务类型注册
container.RegisterType();
MyRepository.cs
公共类MyRepository:IMyRepository
{
IMyDbContext dbContext;
MyDbContext myContext;
公共MyRepository(IMyDbContext _dbContext)
{
dbContext = _dbContext;
myContext =(MyDbProject.MyDbContext)dbContext.MyDbContext;
}
公共列表Get()
{
返回myContext.SP_GetEntity()。ToList();
}
}
MyController.cs
私有只读IMyRepository _repo = null;
// MyController的构造方法
公共MyyController(IMyRepository存储库)
{
_repo = repo;
}
_repo通过使用Unity容器的依赖注入实例化。