Etag concurrency design

302 views
Skip to first unread message

Matthias De Ridder

unread,
Apr 6, 2012, 4:04:19 AM4/6/12
to rav...@googlegroups.com
When an entity is stored in RavenDB, and a property Id exists on the entity, we noticed the property is set by default with the generated Id after a Store().

Why isn't this the case for Etag? We are trying to figure out how we will handle concurrency in our application, using MVC. Optimistic concurrency will be no option. So we will need to use Etag for concurrency checks. This means we always need the reflection to ask the session for the etag number after performing a database action, and setting it:

For a Store() or Load(): 

x.Etag = session.Advanced.GetEtagFor(entity)

When querying: 

session.Query<Entity>().ToList().Select(x =>
                                {
                                    x.Etag = session.Advanced.GetEtagFor(x);
                                    return x;
                                });

If one programmer forgets to set Etag on a entity, concurrency errors can occur.

What was the reason to design Etag like this? Wouldn't it be much nicer and easier to have Etag also be set by default when a Guid? Etag property exists on the entity?

Kind regards,

Matthias.

Itamar Syn-Hershko

unread,
Apr 6, 2012, 4:40:54 AM4/6/12
to rav...@googlegroups.com
The reason is an etag is a server-wide value. Each RavenDB document is being stamped with the server etag that was current when it changed, and that etag is being incremented by one with every such change. This only happens when hitting the server.

Document Ids are given in-memory by the client API (unless you are not using HiLo IDs), this is why it is enough to call Store() to get an ID, without talking to the server

Optimistic concurrency uses the etag mechanism. Why would it be a no go for you? What are you trying to do?

Marco

unread,
Apr 6, 2012, 4:46:32 AM4/6/12
to rav...@googlegroups.com
Sound like a very good idea, and if the etag is set, automatically check for concurrency errors..

Matthias De Ridder

unread,
Apr 6, 2012, 5:06:11 AM4/6/12
to rav...@googlegroups.com
Op vrijdag 6 april 2012 10:40:54 UTC+2 schreef Itamar Syn-Hershko het volgende:

Optimistic concurrency uses the etag mechanism. Why would it be a no go for you? What are you trying to do?
 
When an entity is loaded, it is returned  by a ASP.NET MVC controller to the client as a JQuery JSON object for that entity. The controller then disposes the session. So updates will not occur in the same session. 


Matthias De Ridder

unread,
Apr 6, 2012, 5:35:26 AM4/6/12
to rav...@googlegroups.com
Looking further into this issue, we might have found a way to get this result using the IDocumentStoreListener and IDocumentConversionListener. Is there a reason why we would not use this approach?

        public class TestObject
{
public string Id { get; set; }
public Guid? Etag { get; set; }
}

public class DocumentConversionListener : IDocumentConversionListener 
{
public void DocumentToEntity(object entity, RavenJObject document, RavenJObject metadata)
{
var x = entity as TestObject;
if (x != null)
{
x.Etag = metadata.Value<Guid>("@etag");
}
}

public void EntityToDocument(object entity, RavenJObject document, RavenJObject metadata)
{
return;
}
}

public class DocumentStoreListener : IDocumentStoreListener
{
public void AfterStore(string key, object entityInstance, RavenJObject metadata)
{
var x = entityInstance as TestObject;
if (x != null)
{
x.Etag = metadata.Value<Guid>("@etag");
}
}

public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
return true;
}
}

Marco

unread,
Apr 6, 2012, 5:42:27 AM4/6/12
to rav...@googlegroups.com
But why do you want to store the ETag inside the document? 

You fetch the document, Etag is assigned by the IDocumentConversionListener, the object is shown to the user, hidden property contains the etag from the server, session is closed (asp.net sample)
User modifies the document, hits save, you fetch the document from the server, compare the server-etag with the client-etag and save if  equal else raise exception

Or better, extend the DocumentStoreListener to compare the server-side etag with the local etag.

Op vrijdag 6 april 2012 11:35:26 UTC+2 schreef Matthias De Ridder het volgende:

Itamar Syn-Hershko

unread,
Apr 6, 2012, 5:49:24 AM4/6/12
to rav...@googlegroups.com
Yes, this is going to be a much better approach. Also note that for store you can specify the etag in the function call.

Matthias De Ridder

unread,
Apr 6, 2012, 5:52:25 AM4/6/12
to rav...@googlegroups.com
Marco,

You are right. We weren't planning to save the Etag inside the document, what would be the case in the example I posted. The TestObject should look like:
 
 public class TestObject
{
public string Id { get; set; }
[JsonIgnore]
                public Guid? Etag { get; set; }
}

It's a nice suggestion to perform the check in the BeforeStore.

Op vrijdag 6 april 2012 11:42:27 UTC+2 schreef Marco het volgende:

Oren Eini (Ayende Rahien)

unread,
Apr 6, 2012, 6:12:40 AM4/6/12
to rav...@googlegroups.com
Matthias,
But you don't need to do it this way, you can just call:

Store(entity, id, etag);

And then RavenDB will do the usual concurency checks.

Matthias De Ridder

unread,
Apr 6, 2012, 6:27:09 AM4/6/12
to rav...@googlegroups.com
Thank you, Oren. You are right. If the functionality already exists, we don't need to implement it ourself.

Op vrijdag 6 april 2012 12:12:40 UTC+2 schreef Oren Eini het volgende:

Matthias De Ridder

unread,
Apr 17, 2012, 9:47:13 AM4/17/12
to rav...@googlegroups.com
What is the difference between setting UseOptimisticConcurrency to true or not? 

Will this code:

public void Save<T> (T obj)
{
           Guid eTag = (Guid)Session.Advanced.GetEtagFor(obj);
           Session.Store(obj, eTag);
           Session.SaveChanges(); 
}

behave different then this code?

public void Save<T> (T obj) 
     Session.Advanced.UseOptimisticConcurrency = true;
           Guid eTag = (Guid)Session.Advanced.GetEtagFor(obj); 
           Session.Store(obj, eTag); 
           Session.SaveChanges(); 
}

And if so, how will it behave differently? It's not clear to me why or when to set UseOptimisticConcurrency to true if I have the Etag number anyway.

Oren Eini (Ayende Rahien)

unread,
Apr 17, 2012, 10:09:15 AM4/17/12
to rav...@googlegroups.com
Yes, it will.
The second one will actually do the checks for etag.
The first one will not.

Note that this code is meaningless, regardless of your version:

Matthias De Ridder

unread,
Apr 17, 2012, 10:28:43 AM4/17/12
to rav...@googlegroups.com
So, if I understand correctly:

- setting UseOptimisticConcurrency to true will automatically check Etag as long as you stay in the same session. You only need to use Store(obj)?
- when the object is loaded in session A, goes out of scope of session A, is changed and stored in session B, you must use Store(obj, etag, id)? Is it correct that no concurrency exception will be thrown unless you set UseOptimisticConcurrency to true on session B?

Oren Eini (Ayende Rahien)

unread,
Apr 17, 2012, 10:36:42 AM4/17/12
to rav...@googlegroups.com
inline

On Tue, Apr 17, 2012 at 5:28 PM, Matthias De Ridder <orcr...@gmail.com> wrote:
So, if I understand correctly:

- setting UseOptimisticConcurrency to true will automatically check Etag as long as you stay in the same session. You only need to use Store(obj)?

Yes, but where did this obj came from?
 
- when the object is loaded in session A, goes out of scope of session A, is changed and stored in session B, you must use Store(obj, etag, id)? Is it correct that no concurrency exception will be thrown unless you set UseOptimisticConcurrency to true on session B?

Yes, that is true.

Note that you should make sure to also take the Etag from session A

Matthias De Ridder

unread,
Apr 17, 2012, 10:45:19 AM4/17/12
to rav...@googlegroups.com
Let me rephrase my questions:

- setting UseOptimisticConcurrency to true will automatically check Etag as long as you stay in the same session. You only need to use Store(obj) if obj was loaded in the same session? 
- when the object is loaded in session A, goes out of scope of session A, is changed and stored in session B, you must use Store(obj, etag, id), where etag must be the etag from session A? Is it correct that no concurrency exception will be thrown unless you set UseOptimisticConcurrency to true on session B? With UseOptimisticConcurrency set to false, Store will be executed, but the document will not be changed?



Oren Eini (Ayende Rahien)

unread,
Apr 17, 2012, 10:47:12 AM4/17/12
to rav...@googlegroups.com
inline

On Tue, Apr 17, 2012 at 5:45 PM, Matthias De Ridder <orcr...@gmail.com> wrote:
Let me rephrase my questions:

- setting UseOptimisticConcurrency to true will automatically check Etag as long as you stay in the same session. You only need to use Store(obj) if obj was loaded in the same session? 

Yes
 
- when the object is loaded in session A, goes out of scope of session A, is changed and stored in session B, you must use Store(obj, etag, id), where etag must be the etag from session A?

Yes
 
Is it correct that no concurrency exception will be thrown unless you set UseOptimisticConcurrency to true on session B?

Yes
 
With UseOptimisticConcurrency set to false, Store will be executed, but the document will not be changed?


The doc WILL be changed, when you call SaveChanges()
 

Matthias De Ridder

unread,
Apr 17, 2012, 10:51:29 AM4/17/12
to rav...@googlegroups.com
With UseOptimisticConcurrency set to false, Store will be executed, but the document will not be changed?


The doc WILL be changed, when you call SaveChanges()

Oh. So etag is ignored unless UseOptimisticConcurrency is set to true.

Oren Eini (Ayende Rahien)

unread,
Apr 17, 2012, 10:52:30 AM4/17/12
to rav...@googlegroups.com
Yes

Chris Marisic

unread,
Apr 17, 2012, 3:21:22 PM4/17/12
to rav...@googlegroups.com
This sounds like an error.

Oren, why would you ever ignore what the user says if they use  Session.Store(obj, eTag) and store the object anyway?

That api is a promise to store the specific object with the specific eTag. To store it otherwise is a violation of that promise.

Oren Eini (Ayende Rahien)

unread,
Apr 17, 2012, 8:07:07 PM4/17/12
to rav...@googlegroups.com
I don't have any issue with modifying this, a pull request would be great

Oren Eini (Ayende Rahien)

unread,
Apr 17, 2012, 10:33:41 PM4/17/12
to rav...@googlegroups.com
And it will be in the next build
Reply all
Reply to author
Forward
0 new messages