首页 > 解决方案 > 具有存储库和 UnitOfWork 的 Unity 容器

问题描述

我有一个 ASP.NET WEB API 项目,它使用存储库和工作单元模式以及 UnityContainer。我用 PerRequestLifeTimeManager 注册了我所有的存储库和工作单元类,一切都运行良好。每个客户端对服务器的请求都在与其他客户端请求分开的单个事务中工作。请求返回给客户端后,DbContext 被释放。

当我向客户返回响应后,我需要执行繁重的操作时,我的问题就开始了。DbContext 已处置,我无法向 Unity Container 请求新实例(因为不存在 HttpContext )。

我读了这篇文章PerRequestLifetimeManager can only be used in the context of an HTTP request 并尝试实现 2 个不同的 UnityContainer

  1. WebContainer - 使用 PerRequestLifeTimeManager 注册所有类
  2. CorContainer - 使用 PerResolveLifetimeManager 注册所有类

我以为我解决了这个问题,但我注意到我从 CoreContainer 请求的每个存储库都使用不同的 DbContext。UnitOfWork 也有不同的 DbContext。这当然会导致我的代码中出现许多错误。

这是我用来在 UnityContainer 中注册我的实体的代码。我有 2 个数据库,所以一些存储库使用第一个数据库的 DbContext,另一个使用第二个 DbContext。我的 UnitOfWork 使用了两个 DbContexts

public static class UnityConfig
{
    #region Unity Container
    private static Lazy<IUnityContainer> perRequestContainer =
      new Lazy<IUnityContainer>(() =>
      {
          var container = new UnityContainer();
          RegisterCommonTypes<PerRequestLifetimeManager>(container);
          return container;
      });

    private static Lazy<IUnityContainer> perResolveContainer =
      new Lazy<IUnityContainer>(() =>
      {
          var container = new UnityContainer();
          RegisterCommonTypes<PerResolveLifetimeManager>(container);
          //CNSDeployerService can 
          container.RegisterType<ICNSDeployerService, CNSDeployerService>(new PerResolveLifetimeManager());
          return container;
      });


    public static IUnityContainer WebContainer => perRequestContainer.Value;
    public static IUnityContainer CoreContainer => perResolveContainer.Value;
    #endregion

    /// <summary>
    /// Please notice this configuration exist only inside the scope of a single request 
    /// See UnityWebApiActivator
    /// </summary>
    /// <param name="container"></param>
    public static void RegisterCommonTypes<T>(IUnityContainer container) where T: LifetimeManager
    {
        container.RegisterType<ApplicationDbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
        container.RegisterType<DbContext, ApplicationDbContext>(Activator.CreateInstance<T>());
        container.RegisterType<capNsubContext, capNsubContext>(Activator.CreateInstance<T>());

        container.RegisterType<IDataContextAsync, capNsubContext>("capNsubContext", Activator.CreateInstance<T>());
        container.RegisterType<IDataContextAsync, LinetServerContext>("linnetDataContext", Activator.CreateInstance<T>());
        container.RegisterType<IRepository<Entity>, Repository<Entity>>(Activator.CreateInstance<T>());
        container.RegisterType<IRepositoryAsync<VideoTargetLanguage>, Repository<VideoTargetLanguage>>(Activator.CreateInstance<T>());
        //Unity understand array by defaults so we just need to map it to IEnumerable
        container.RegisterType<IEnumerable<IDataContextAsync>, IDataContextAsync[]>();
        container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(Activator.CreateInstance<T>());

        container.RegisterType<UserManager<ApplicationUser>>(Activator.CreateInstance<T>());
        container.RegisterType<RoleManager<IdentityRole>>(Activator.CreateInstance<T>());
        container.RegisterType<AccountController>(Activator.CreateInstance<T>());

        container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(Activator.CreateInstance<T>());
        container.RegisterType<IOrderService, OrderService>(Activator.CreateInstance<T>());
        container.RegisterType<IFFMpegService, FFMpegService>(Activator.CreateInstance<T>());
        container.RegisterType<IVideoService, VideoService>(Activator.CreateInstance<T>());
        container.RegisterType<IOrderItemService, OrderItemService>(Activator.CreateInstance<T>());
        container.RegisterType<ILanguageService, LanguageService>(Activator.CreateInstance<T>());
        container.RegisterType<IUserService, UserService>(Activator.CreateInstance<T>());

        container.RegisterType<ICNSCaptionsService, CNSCaptionsService>(Activator.CreateInstance<T>());
        container.RegisterType<ICNSTranslationsService, CNSTranslationsService>(Activator.CreateInstance<T>());
        container.RegisterType<ICNSCapMoviesService, CNSMovieService>(Activator.CreateInstance<T>());
        container.RegisterType<HttpClient, HttpClient>(Activator.CreateInstance<T>());
        container.RegisterType<SimpleRefreshTokenProvider, SimpleRefreshTokenProvider>(Activator.CreateInstance<T>());

        var capNsubEntityTypes = GetEntityFrameworkEntityTypesByContext<capNsubContext>();
        var linnetEntityTypes = GetEntityFrameworkEntityTypesByContext<LinetServerContext>();
        RegisterEntitiesRepostiories<T>(container, capNsubEntityTypes, "capNsubContext");
        RegisterEntitiesRepostiories<T>(container, linnetEntityTypes, "linnetDataContext");
    }

    private static void RegisterEntitiesRepostiories<T>(IUnityContainer container, IEnumerable<Type> entities, string contextName)
        where T:LifetimeManager
    {
        var iGenericRepositoryTypes = new[] { typeof(IRepositoryAsync<>), typeof(IRepository<>) };
        foreach (var iGenericRepositoryType in iGenericRepositoryTypes)
        {
            foreach (var entityType in entities)
            {
                var iSpecificRepositoryType = iGenericRepositoryType.MakeGenericType(entityType);
                var genericRepositoryType = typeof(Repository<>);
                var specificRepositoryType = genericRepositoryType.MakeGenericType(entityType);

                container.RegisterType(iSpecificRepositoryType, Activator.CreateInstance<T>(), new InjectionFactory(c =>
                {
                    return Activator.CreateInstance(specificRepositoryType, c.Resolve<IDataContextAsync>(contextName), c.Resolve<IUnitOfWorkAsync>());
                }));
            }
        }
    }

    private static IEnumerable<Type> GetEntityFrameworkEntityTypesByContext<T>() where T : DataContext
    {
        var capNsubContextType = typeof(T);
        var capNsubDataAssembly = Assembly.GetAssembly(capNsubContextType);
        var ef6EntityType = typeof(Repository.Pattern.Ef6.Entity);

        return capNsubDataAssembly.GetTypes()
                  .Where(t => String.Equals(t.Namespace, capNsubContextType.Namespace, StringComparison.Ordinal) &&
                    t.IsSubclassOf(ef6EntityType));

    }
}


[System.Web.Http.Authorize(Roles = "admin")]
[System.Web.Http.RoutePrefix("api/job")]
public class JobController : BaseApiController { 


    [System.Web.Http.Route("Create", Name = "Create")]
    [System.Web.Http.HttpPost]
    public IHttpActionResult Create(JobBindingModel createJobModal)
    {
        //We have to use the CoreContainer since cnsDeployer scope runs outside of the request
        var cnsDeployer = UnityConfig.CoreContainer.Resolve<ICNSDeployerService>();

        if (!ModelState.IsValid)
        {
            return BadRequest();
        }

        try
        {
            //This runs in the backround after we return the response to client
            cnsDeployer.Deploy(createJobModal.ItemIds);
            return Ok();
        }
        catch(Exception err)
        {
            return InternalServerError(err);
        }

    }
}

 public class CNSDeployerService : ICNSDeployerService
{
    private readonly IOrderItemService orderItemService;
    private readonly ICNSCapMoviesService cnsMoviesService;
    private readonly ICNSTranslationsService cnsTranslationsService;
    private readonly IFFMpegService ffMpegService;
    private readonly IUnitOfWorkAsync unitOfWorkAsync;
    private readonly IVideoService videoService;

    public CNSDeployerService(IOrderItemService orderItemService,
                ICNSCapMoviesService cnsCapMoviesService,
                ICNSTranslationsService cnsTranslationsService,
                IFFMpegService ffMpegService,
                IUnitOfWorkAsync unitOfWorkAsync,
                IVideoService videoService)
    {
        this.orderItemService = orderItemService;
        this.cnsMoviesService = cnsCapMoviesService;
        this.cnsTranslationsService = cnsTranslationsService;
        this.ffMpegService = ffMpegService;
        this.unitOfWorkAsync = unitOfWorkAsync;
        this.videoService = videoService;
    }

    public void Deploy(IEnumerable<Guid> orderItemIds)
    {
        try
        {
            InnerDeploy(orderItemIds);
        }
        catch
        {
            unitOfWorkAsync.Dispose();
        }

    }

    private void InnerDeploy(IEnumerable<Guid> orderItemIds)
    {
        var orderItems = orderItemService.Queryable()
          .Where(orderItem => orderItemIds.Any(itemId => orderItem.Id == itemId)
              && orderItem.IsInProcessQueue == false
              && !orderItem.TranslationId.HasValue)
          .ToList();

        if (orderItems.Count == 0)
        {
            unitOfWorkAsync.Dispose();
            throw new ArgumentNullException("No valid orders was provided");
        }

        foreach ( var orderItem in orderItems)
         {
            orderItem.IsInProcessQueue = true;
            orderItemService.Update(orderItem);
        }

         unitOfWorkAsync.SaveChanges();

        var translationId = Guid.NewGuid();
        var movieId = Guid.NewGuid();
        var connectedMoviePath = cnsMoviesService.GetMoviePath(movieId);
        var videosUrlList = orderItems
            .Select(orderItem => orderItem.VideosTable.VideoUrl)
            .ToList();

        //Don't await on this task since we want concat to continue after request is returned
        Task.Run(async () =>
        {
            try
            {
                await ffMpegService.ConcatVideos(videosUrlList, connectedMoviePath);
                VideoUtils.CreateVideoImageAndReturnPath(connectedMoviePath);

                var videosTotalDuration = videoService.GetVideosTotalDuration(orderItemIds);
                var durationInSeconds = Convert.ToInt32((int)(videosTotalDuration / 1000));
                await cnsMoviesService.CreateMovieRecordAsync(movieId, durationInSeconds);
                await cnsTranslationsService.CreateTranslationRecordAsync(movieId, translationId, language: 1);

                var index = 0;
                foreach (var orderItem in orderItems)
                {
                    orderItem.TranslationId = translationId;
                    orderItem.TranslationIndex = index++;
                    orderItem.IsInProcessQueue = false;
                    orderItemService.Update(orderItem);
                }

                await unitOfWorkAsync.SaveChangesAsync();

            }
            catch (Exception err)
            {
                //TODO: Handle error
            }
            finally
            {
                //Dispose db context
                unitOfWorkAsync.Dispose();
            }

        });
    }


}

标签: c#entity-frameworkasp.net-web-apiunity-containerunit-of-work

解决方案


推荐阅读