问题
I'm building a .Net Web API which uses a Service+Repository pattern w/ Entity Framework. Each controller's CRUD actions relay data retrieved by calls to a Service.
I have a SomeContext that extends DbContext:
public class SomeContext : DbContext
{
public SomeContext(string connString) : base(connString) { }
// DbSets
...
}
The Service is initialized w/ a constructor that accepts an ISomeContext:
public class Service : IService
{
public Service(ISomeContext ctx) : base(ctx)
{
_alpha = new AlphaRepository(ctx);
...
}
GetAllAlpha()
{
return _alpha.Get();
}
...
}
I want to use (Unity Container) Dependency Injection to inject an instance of SomeContext into the Service constructor. The life cycle of a given SomeContext should be the duration of the API request. The difficulty is that the connection string for SomeContext is dynamic and cannot be known until a runtime parameter 'client' is provided as part of the API request.
Furthermore, there are an indeterminate number of clients and therefore connection strings due to my database-per-tenant environment. As such, I cannot just register n known SomeContexts and Resolve() based on the 'client' parameter.
Instead, an internally developed NuGet package w/ exposed ContextFactory lets me retrieve the appropriate SomeContext for a client:
ContextFactory.GetClientContext(client);
How and where do I configure Unity Container to manage this dynamic SomeContext?
Additional notes:
- Unity is already dependency injecting the IService Service into each of my Controller actions. Because of this, the Service constructor is executed prior to any of the Web API ActionFilters I have created. This means I can't identify the 'client' prior to the injection...I presume this means I would need to use a factory and/or delegate...?
- I've read about using an abstract factory in conjunction with a delegate for the DbContext,
public delegate IDbContext CreateDbContext(string client);
, and adapter for the NuGet package's GetClientContext request, but I haven't been able to piece it all together into a working solution.
Thanks for any help!
回答1:
I don't think you should pass the context as a dependency, the context should be as short lived as possible, disposing it after every use. Here are some examples from my IDataService (which is injected using dep inj in my other services/facades)
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;
}
...
My IDataService has two implementations, one for online mode, one offline, setup like this in Unity (which a factory decides which one to use if wifi is enabled or not, could be something similar to inject proper DataService using different connection strings):
unityContainer.RegisterType<IDataService, OfflineDataService>("OfflineDataService", new ContainerControlledLifetimeManager(), new InjectionConstructor(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ServiceLocator.Current.GetInstance<IMapper>()));
unityContainer.RegisterType<IDataService, DataService>(new ContainerControlledLifetimeManager());
I would suggest placing a factory method here to determine at runtime the proper client (replace Entities.Create() with your injected entity creator factory):
using (var ymse = YMSEntities.Create())
As for your comment about using transactions, in my opinion, the data layer shouldn't care about transactions, only the business layer which deals with many entities. Here is how I do it in my facade (multiple dataservice calls):
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);
}
Transaction code is all in one place shared by all:
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;
}
}
回答2:
The trick to incorporate runtime parameters into a Unity Dependency Injected object is 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
In this case, I designate the static ContextFactoryAdapter.GetSomeContext() method to return a dynamic SomeContext according to data available in the supplied HttpContextWrapper argument:
public static SomeContext GetSomeContext(HttpContextWrapper requestWrapper)
{
var client = requestWrapper.Request.QueryString["client"];
return ContextFactory.GetClientContext(client);
}
So, Unity will resolve ISomeContext type to the SomeContext returned by GetClientContext().
The PerRequestLifetimeManager()
argument to RegisterType() instructs Unity to use the returned SomeContext instance for the lifetime of a single HTTP request. For Unity to dispose of the instance automatically, you must also also register the UnityPerRequestHttpModule in UnityMvcActivator.cs:
DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
With these configurations in place, Unity is able to resolve an appropriate SomeContext and supply this instance to the Service via constructor injection.
回答3:
I have used Unity Container to get dynamic DbContext in Repository/Service. Following code worked for me. Follow these steps
IMyDbContext.cs
public interface IMyDbContext: IDisposable
{
DbContextConfiguration Configuration { get; }
Database Database { get; }
DbSet Set() where T : class;
DbSet Set(Type type);
DbEntityEntry Entry(T entity) where T : class;
int SaveChanges();
Object MyContext { get; }
}MyDbContext.cs
public partial class MyDbContext : DbContext, IMyDbContext
{
Object IMyDbContext.MyDbContext
{
get
{
return new MyDbProject.MyDbContext();
}
}
}UnityConfig.cs file
// Database Types Registration
container.RegisterType();
// Repository/Services Types Registration
container.RegisterType();MyRepository.cs
public class MyRepository : IMyRepository
{
IMyDbContext dbContext;
MyDbContext myContext;
public MyRepository(IMyDbContext _dbContext)
{
dbContext = _dbContext;
myContext = (MyDbProject.MyDbContext)dbContext.MyDbContext;
}
public List Get()
{
return myContext.SP_GetEntity().ToList();
}
}MyController.cs
private readonly IMyRepository _repo = null;
// Constructor of MyController
public MyyController(IMyRepository repo)
{
_repo = repo;
}
The _repo is instantiated by Dependency Injection using Unity Container.
来源:https://stackoverflow.com/questions/50882647/unity-container-dependency-injection-of-dynamic-dbcontext-into-service