using RavenDB with ServiceStack
Asked Answered
P

4

5

I read this post by Phillip Haydon about how to use NHibernate/RavenDB with ServiceStack.
I don't see the point about getting the IDocumentStore and open new session every time i need something from the db like this:

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

Why cant i just use one session per request and when the request is ended, commit the changes or roll them back according to the response status?

If my approach is fine, than how can i implement it? here is my attempt:

I created this class:

    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

    }

And now my service looks like this:

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

but i still need to find a way to know when the request is ended for commit/rollback the changes.
the best way i found is by using 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...
    }
}

I am sure that there is a better way to do this but how?

Poore answered 10/8, 2012 at 11:59 Comment(0)
B
10

I just included this on the Configure method for the 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);

You can put it aside on module and initialize it.

Then in your services just add a constructor that accepts IDocumentSession

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

And you're good to go.

Beatrisbeatrisa answered 7/12, 2012 at 21:54 Comment(2)
Seems like a good idea. But will this close the session after each request? I guess this is the same as the using pattern? But I actually don't understand how it could be the sameWharf
Yeah, when I set the ReusedWithin it makes sure to dispose of the session for each Request.Beatrisbeatrisa
J
2

Filtering the response in ServiceStack

The ways to introspect the Response in ServiceStack is with either:

Some other notes that might be helpful:

ServiceStack's built-in IOC (Funq) now supports RequestScope

You can add IDisposable to a base class which gets called immediately after the service has finished executing, e.g. if you were to use an RDBMS:

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();
    }
}
Josuejosy answered 10/8, 2012 at 16:41 Comment(0)
N
1

I tried the answer given by Felipe Leusin but it has not worked for me. The main thing that I want to achieve is having a single DocumentSession.SaveChanges call per request. After looking at the RacoonBlog DocumentSession lifecycle management and at ServiceStack request lifecycle events I put together a configuration that works for me:

    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);
Nomination answered 4/1, 2013 at 15:22 Comment(3)
you need to register the IDocumentSession to the container with the Request scopePoore
I have tried to do that as well. I prefer at this point to keep my implementation as close as possible with RacoonBlog - if it works for Ayende it will work for me as well.Nomination
Rather than manually registering the document session, use the container registration from Felipe Leusin's answer, then include the response filter from this answer for the best of both worlds.Satrap
P
0

I am using funq with RequestScope for my RavenSession, and now i update it to:

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
}

but this would not work because:
1) although i am using RequestScope, no one is register the IRequestContext of the request so funq cant resolve my RavenSession.
2) funq does not run the Dispose method after the request is done, which is odd.

Poore answered 11/8, 2012 at 0:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.