Search code examples
ravendboptimistic-concurrency

RavenDB Catch 22 - Optimistic Concurrency AND Seeing Changes from Other Clients


With RavenDB, creating an IDocumentSession upon app start-up (and never closing it until the app is closed), allows me to use optimistic concurrency by doing this:

public class GenericData : DataAccessLayerBase, IGenericData
{
    public void Save<T>(T objectToSave)
    {
        Guid eTag = (Guid)Session.Advanced.GetEtagFor(objectToSave);
        Session.Store(objectToSave, eTag);
        Session.SaveChanges();
    }
}

If another user has changed that object, then the save will correctly fail.

But what I can't do, when using one session for the lifetime of an app, is seeing changes, made by other instances of the app (say, Joe, five cubicles away), to documents. When I do this, I don't see Joe's changes:

public class CustomVariableGroupData : DataAccessLayerBase, ICustomVariableGroupData
{
    public IEnumerable<CustomVariableGroup> GetAll()
    {
        return Session.Query<CustomVariableGroup>();
    }
}

Note: I've also tried this, but it didn't display Joe's changes either:

return Session.Query<CustomVariableGroup>().Customize(x => x.WaitForNonStaleResults());

Now, if I go the other way, and create an IDocumentSession within every method that accesses the database, then I have the opposite problem. Because I have a new session, I can see Joe's changes. Buuuuuuut... then I lose optimistic concurrency. When I create a new session before saving, this line produces an empty GUID, and therefore fails:

Guid eTag = (Guid)Session.Advanced.GetEtagFor(objectToSave);

What am I missing? If a Session shouldn't be created within each method, nor at the app level, then what is the correct scope? How can I get the benefits of optimistic concurrency and the ability to see others' changes when doing a Session.Query()?


Solution

  • Disclaimer: I know this can't be the long-term approach, and therefore won't be an accepted answer here. However, I simply need to get something working now, and I can refactor later. I also know some folks will be disgusted with this approach, lol, but so be it. It seems to be working. I get new data with every query (new session), and I get optimistic concurrency working as well.

    The bottom line is that I went back to one session per data access method. And whenever a data access method does some type of get/load/query, I store the eTags in a static dictionary:

    public IEnumerable<CustomVariableGroup> GetAll()
    {
        using (IDocumentSession session = Database.OpenSession())
        {
            IEnumerable<CustomVariableGroup> groups = session.Query<CustomVariableGroup>();
            CacheEtags(groups, session);
            return groups;
        }
    }
    

    Then, when I'm saving data, I grab the eTag from the cache. This causes a concurrency exception if another instance has modified the data, which is what I want.

    public void Save(EntityBase objectToSave)
    {
        if (objectToSave == null) { throw new ArgumentNullException("objectToSave"); }
    
        Guid eTag = Guid.Empty;
    
        if (objectToSave.Id != null)
        {
            eTag = RetrieveEtagFromCache(objectToSave);
        }
    
        using (IDocumentSession session = Database.OpenSession())
        {
            session.Advanced.UseOptimisticConcurrency = true;
            session.Store(objectToSave, eTag);
            session.SaveChanges();
            CacheEtag(objectToSave, session);  // We have a new eTag after saving.
        }
    }
    

    I absolutely want to do this the right way in the long run, but I don't know what that way is yet.

    Edit: I'm going to make this the accepted answer until I find a better way.