Использование RavenDB с ServiceStack

Я прочитал этот пост от Филиппа Хейдона о том, как использовать NHibernate/RavenDB с ServiceStack.
Я не вижу смысла получать IDocumentStore и открывать новый сеанс каждый раз, когда мне нужно что-то из БД, например:

public class FooService : ServiceBase<Foo>
{
    public IDocumentStore RavenStore{ get; set; }

    protected override object Run(ProductFind request)
    {
        using (var session = RavenStore.OpenSession())
        {
            // Do Something...

            return new FooResponse{/*Object init*/};
        }
    }
}

Почему я не могу просто использовать один сеанс для каждого запроса, а когда запрос завершен, зафиксировать изменения или откатить их в соответствии с состоянием ответа?

Если мой подход в порядке, то как я могу его реализовать? вот моя попытка:

Я создал этот класс:

    public class RavenSession : IRavenSession
    {
        #region Data Members

        private readonly IDocumentStore _store;
        private IDocumentSession _innerSession;

        #endregion

        #region Properties

        public IDocumentSession InnerSession
        {
            get { return _innerSession ?? (_innerSession = _store.OpenSession()); }
        }

        #endregion

        #region Ctor

        public RavenSession(IDocumentStore store)
        {
            _store = store;
        }

        #endregion

        #region Public Methods

        public void Commit()
        {
            if (_innerSession != null)
            {
                try
                {
                    InnerSession.SaveChanges();
                }
                finally
                {
                    InnerSession.Dispose();
                }
            }
        }

        public void Rollback()
        {
            if (_innerSession != null)
            {
                InnerSession.Dispose();
            }
        }

        #endregion

        #region IDocumentSession Delegation

        public ISyncAdvancedSessionOperation Advanced
        {
            get { return InnerSession.Advanced; }
        }

        public void Delete<T>(T entity)
        {
            InnerSession.Delete(entity);
        }

        public ILoaderWithInclude<object> Include(string path)
        {
            return InnerSession.Include(path);
        }

        public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
        {
            return InnerSession.Include<T, TInclude>(path);
        }

        public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
        {
            return InnerSession.Include(path);
        }

        public T Load<T>(string id)
        {
            return InnerSession.Load<T>(id);
        }

        public T[] Load<T>(params string[] ids)
        {
            return InnerSession.Load<T>(ids);
        }

        public T Load<T>(ValueType id)
        {
            return InnerSession.Load<T>(id);
        }

        public T[] Load<T>(IEnumerable<string> ids)
        {
            return InnerSession.Load<T>(ids);
        }

        public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
        {
            return InnerSession.Query<T, TIndexCreator>();
        }

        public IRavenQueryable<T> Query<T>()
        {
            return InnerSession.Query<T>();
        }

        public IRavenQueryable<T> Query<T>(string indexName)
        {
            return InnerSession.Query<T>(indexName);
        }

        public void Store(dynamic entity, string id)
        {
            InnerSession.Store(entity, id);
        }

        public void Store(object entity, Guid etag, string id)
        {
            InnerSession.Store(entity, etag, id);
        }

        public void Store(object entity, Guid etag)
        {
            InnerSession.Store(entity, etag);
        }

        public void Store(dynamic entity)
        {
            InnerSession.Store(entity);
        }

        #endregion

    }

И теперь мой сервис выглядит так:

public class FooService : ServiceBase<Foo>
{
    public IRavenSession RavenSession { get; set; }

    protected override object Run(ProductFind request)
    {
        // Do Something with RavenSession...

        return new FooResponse {/*Object init*/};
    }
}

но мне все еще нужно найти способ узнать, когда запрос заканчивается для фиксации / отката изменений.
лучший способ, который я нашел, это с помощью ResponseFilters:

public class AppHost : AppHostBase
{
    public AppHost()
        : base("", typeof (Foo).Assembly, typeof (FooService).Assembly)
    {
    }

    public override void Configure(Container container)
    {
        // Some Configuration...

        this.ResponseFilters.Add((httpReq, httpResp, respnseDto) =>
                                     {
                                         var currentSession = (RavenSession) this.Container.Resolve<IRavenSession>();

                                         if (!httpResp.IsErrorResponse())
                                         {
                                             currentSession.Commit();
                                         }
                                         else
                                         {
                                             currentSession.Rollback();
                                         }
                                     });

        // Some Configuration...
    }
}

Я уверен, что есть лучший способ сделать это, но как?

4 ответа

Я просто включил это в метод Configure для AppHost

var store = new DocumentStore()
{
    Url = "http://127.0.0.1:8080",
    DefaultDatabase = "Test"
}.Initialize();

container.Register(store);

container.Register(c => c.Resolve<IDocumentStore>().OpenSession()).ReusedWithin(ReuseScope.Request);

Вы можете отложить его в модуль и инициализировать.

Затем в ваших сервисах просто добавьте конструктор, который принимает IDocumentSession

public HelloService : Service {
    private readonly IDocumentSession session;
    public HelloService(IDocumentSession session) {
        this.session = session;
    }
}

И ты в порядке.

Фильтрация ответа в ServiceStack

Способы анализа ответа в ServiceStack могут быть следующими:

Некоторые другие заметки, которые могут быть полезны:

Встроенный IOC (Funq) ServiceStack теперь поддерживает RequestScope

Можете добавить IDisposable к базовому классу, который вызывается сразу после завершения службы, например, если вы собираетесь использовать СУБД:

public class FooServiceBase : IService, IDisposable
{
    public IDbConnectionFactory DbFactory { get; set; }

    private IDbConnection db;
    public IDbConnection Db
    {
        get { return db ?? (db = DbFactory.OpenDbConnection()); }
    }

    public object Any(ProductFind request)
    {
        return new FooResponse {
            Result = Db.Id<Product>(request.Id)
        };
    }

    public void Dispose()
    {
        if (db != null) db.Dispose();
    }
}

Я попробовал ответ Фелипе Леусина, но он не сработал для меня. Главное, чего я хочу добиться, - это один вызов DocumentSession.SaveChanges на запрос. Посмотрев на управление жизненным циклом RacoonBlog DocumentSession и события жизненного цикла запроса ServiceStack, я собрал конфигурацию, которая работает для меня:

    public override void Configure(Funq.Container container)
    {
        RequestFilters.Add((httpReq, httpRes, requestDto) =>
            {

                IDocumentSession documentSession = Container.Resolve<IDocumentStore>().OpenSession();
                Container.Register<IDocumentSession>(documentSession);
            });

        ResponseFilters.Add((httpReq, httpRes, requestDto) =>
            {
                using (var documentSession = Container.Resolve<IDocumentSession>())
                {
                    if (documentSession == null)
                        return;

                    if (httpRes.StatusCode >= 400 && httpRes.StatusCode < 600)
                        return;

                    documentSession.SaveChanges();
                }
            });
        var documentStore = new DocumentStore
            {
                ConnectionStringName = "RavenDBServer",
                DefaultDatabase = "MyDatabase",
            }.Initialize();

        container.Register(documentStore);

Я использую funq с RequestScope для моей RavenSession, и теперь я обновляю его до:

public class RavenSession : IRavenSession, IDisposable
{
    #region Data Members

    private readonly IDocumentStore _store;
    private readonly IRequestContext _context;
    private IDocumentSession _innerSession;

    #endregion

    #region Properties

    public IDocumentSession InnerSession
    {
        get { return _innerSession ?? (_innerSession = _store.OpenSession()); }
    }

    #endregion

    #region Ctor

    public RavenSession(IDocumentStore store, IRequestContext context)
    {
        _store = store;
        _context = context;
    }

    #endregion

    #region IDocumentSession Delegation

    public ISyncAdvancedSessionOperation Advanced
    {
        get { return InnerSession.Advanced; }
    }

    public void Delete<T>(T entity)
    {
        InnerSession.Delete(entity);
    }

    public ILoaderWithInclude<object> Include(string path)
    {
        return InnerSession.Include(path);
    }

    public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
    {
        return InnerSession.Include<T, TInclude>(path);
    }

    public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
    {
        return InnerSession.Include(path);
    }

    public T Load<T>(string id)
    {
        return InnerSession.Load<T>(id);
    }

    public T[] Load<T>(params string[] ids)
    {
        return InnerSession.Load<T>(ids);
    }

    public T Load<T>(ValueType id)
    {
        return InnerSession.Load<T>(id);
    }

    public T[] Load<T>(IEnumerable<string> ids)
    {
        return InnerSession.Load<T>(ids);
    }

    public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
    {
        return InnerSession.Query<T, TIndexCreator>();
    }

    public IRavenQueryable<T> Query<T>()
    {
        return InnerSession.Query<T>();
    }

    public IRavenQueryable<T> Query<T>(string indexName)
    {
        return InnerSession.Query<T>(indexName);
    }

    public void Store(dynamic entity, string id)
    {
        InnerSession.Store(entity, id);
    }

    public void Store(object entity, Guid etag, string id)
    {
        InnerSession.Store(entity, etag, id);
    }

    public void Store(object entity, Guid etag)
    {
        InnerSession.Store(entity, etag);
    }

    public void Store(dynamic entity)
    {
        InnerSession.Store(entity);
    }

    #endregion

    #region Implementation of IDisposable

    public void Dispose()
    {
        if (_innerSession != null)
        {
            var httpResponse = _context.Get<IHttpResponse>();

            try
            {
                if (!httpResponse.IsErrorResponse())
                {
                    _innerSession.SaveChanges();
                }
            }
            finally
            {
                _innerSession.Dispose();
            }
        }
    }

    #endregion
}

но это не сработает, потому что:
1) хотя я использую RequestScope, никто не регистрирует IRequestContext запроса, поэтому funq не может разрешить мою RavenSession.
2) funq не запускает метод Dispose после выполнения запроса, что странно.

Другие вопросы по тегам