代码之家  ›  专栏  ›  技术社区  ›  Tony

WebApi:如何使用IoC实现每个请求每个操作的DbSession?

  •  1
  • Tony  · 技术社区  · 8 年前

    我们现有的数据库部署有一个“主”和一个只读副本。使用ASP。NET的Web API2和IoC容器,我想创建控制器操作,其属性(或缺少属性)指示该请求将使用哪个数据库连接(请参阅下面的控制器和服务使用情况)。。。

    public MyController :  ApiController
    {
        public MyController(IService1 service1, IService2 service2) { ... }
    
        // this action just needs the read only connection
        // so no special attribute is present
        public Foo GetFoo(int id)
        {
            var foo = this.service1.GetFoo(id);
            this.service2.GetSubFoo(foo);
            return foo;
        }
    
        // This attribute indicates a readwrite db connection is needed
        [ReadWrteNeeded]
        public Foo PostFoo(Foo foo)
        {
            var newFoo = this.service1.CreateFoo(foo);
            return newFoo;
        }
    }
    
    public Service1 : IService1
    {
        // The dbSession instance injected here will be
        // based off of the action invoked for this request
        public Service1(IDbSession dbSession) { ... }
    
        public Foo GetFoo(int id) 
        { 
            return this.dbSession.Query<Foo>(...);
        }
    
        public Foo CreateFoo(Foo newFoo)
        {
            this.dbSession.Insert<Foo>(newFoo);
            return newFoo;
        }
    }
    

    然而,我不确定如何为请求生成IDbSession实例的类型,以关闭匹配控制器动作的指示器属性(或缺少)。我假设我需要创建一个ActionFilter,它将查找指示符属性,并使用该信息识别或创建正确类型的IDbSession(只读或读写)。但是,如何确保创建的IDbSession的生命周期由容器管理?你不在运行时将实例注入容器,那将是愚蠢的。我知道过滤器在启动时创建一次(使其成为单例),所以我无法将值注入过滤器的向量。

    思想?

    更新:

    我开始使用史蒂文提出的制作DbSessionProxy的建议。这很管用,但我真的在寻找一个纯粹的国际奥委会解决方案。必须使用HttpContext和/或(在我的情况下)请求。我只是觉得这里有点脏。所以,如果我不得不脏的话,我最好一路走,对吗?

    注射 将当前HttpRequestMessage导入服务(这很重要)。以下是我所做的。。。

    IoC容器设置:

    For<IDbSession>().Use(() => DbSession.ReadOnly()).Named("ReadOnly");
    For<IDbSession>().Use(() => DbSession.ReadWrite()).Named("ReadWrite");
    For<ISampleService>().Use<SampleService>();
    

    public class DbAccessAttribute : ActionFilterAttribute
    {
        private readonly DbSessionType dbType;
    
        public DbAccessAttribute(DbSessionType dbType)
        {
            this.dbType = dbType;
        }
    
        public override bool AllowMultiple => false;
    
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var container = (IContainer)actionContext.GetService<IContainer>();
            var dbSession = this.dbType == DbSessionType.ReadOnly ?
                    container.GetInstance<IDbSession>("ReadOnly") :
                    container.GetInstance<IDbSession>("ReadWrite");
    
            // if this is a ReadWrite HttpRequest start an Request long
            // database transaction
            if (this.dbType == DbSessionType.ReadWrite)
            {
                dbSession.Begin();
            }
    
            actionContext.Request.Properties["DbSession"] = dbSession;
        }
    
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var dbSession = (IDbSession)actionExecutedContext.Request.Properties["DbSession"];
            if (this.dbType == DbSessionType.ReadWrite)
            {
                // if we are responding with 'success' commit otherwise rollback
                if (actionExecutedContext.Response != null &&
                    actionExecutedContext.Response.IsSuccessStatusCode &&
                    actionExecutedContext.Exception == null)
                {
                    dbSession.Commit();                    
                }
                else
                {
                    dbSession.Rollback();
                }
            }
        }
    }
    

    更新的服务1:

    public class Service1: IService1
    {
        private readonly HttpRequestMessage request;
        private IDbSession dbSession;
    
        public SampleService(HttpRequestMessage request)
        {
            // WARNING: Never attempt to access request.Properties[Constants.RequestProperty.DbSession]
            // in the ctor, it won't be set yet.
            this.request = request;
        }
    
        private IDbSession Db => (IDbSession)request.Properties["DbSession"];
    
        public Foo GetFoo(int id) 
        { 
            return this.Db.Query<Foo>(...);
        }
    
        public Foo CreateFoo(Foo newFoo)
        {
            this.Db.Insert<Foo>(newFoo);
            return newFoo;
        }
    }
    
    1 回复  |  直到 8 年前
        1
  •  1
  •   Steven    8 年前

    我假设我需要创建一个ActionFilter,它将查找指示符属性,并使用该信息识别或创建正确类型的IDbSession(只读或读写)。

    explicitly modelled behind a generic abstraction ,因为在这种情况下,您可以将该属性放置在业务操作中,并且当您显式地将读操作与写操作(CQ/CQR)分离时,您甚至可能根本不需要该属性。但我现在会认为这超出了你的问题范围,所以这意味着ActionFilter是适合你的方式。

    但是,如何确保创建的IDbSession的生命周期由容器管理?

    诀窍是让ActionFilter存储关于在请求全局值中使用哪个数据库的信息。这允许您为创建代理实现 IDbSession 它能够基于此设置在内部可读写实现之间切换。

    public class ReadWriteSwitchableDbSessionProxy : IDbSession
    {
        private readonly IDbSession reader;
        private readonly IDbSession writer;
    
        public ReadWriteSwitchableDbSessionProxy(
            IDbSession reader, IDbSession writer) { ... }
    
        // Session operations
        public IQueryable<T> Set<T>() => this.CurrentSession.Set<T>();
    
        private IDbSession CurrentSession
        {
            get
            {
                var write = (bool)HttpContext.Current.Items["WritableSession"];
    
                return write ? this.writer : this.reader;
            }
        }
    }
    
    推荐文章