Unity Container - Dependency Injection of Dynamic DbContext into Service

北城余情 提交于 2019-12-03 21:26:45

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;
        }
    }

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.

I have used Unity Container to get dynamic DbContext in Repository/Service. Following code worked for me. Follow these steps

  1. 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; }
    }

  2. MyDbContext.cs

    public partial class MyDbContext : DbContext, IMyDbContext
    {
    Object IMyDbContext.MyDbContext
    {
    get
    {
    return new MyDbProject.MyDbContext();
    }
    }
    }

  3. UnityConfig.cs file

    // Database Types Registration
    container.RegisterType();
    // Repository/Services Types Registration
    container.RegisterType();

  4. 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();
    }
    }

  5. 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!