fnf
fnf

Reputation: 414

How to handle an entity with two "unique" properties?

Consider a web-based system where multiple users might be creating accounts at the same time. The system requires that users have an unique username as well as an unique e-mail address. In an RDBMS, this would be simple: the "email" field would be marked as UNIQUE. How could I approach this the datastore way?

This was my initial attempt:

// Create entity using the username as key
String username = ... // provided by the user
String email = ...    // provided by the user
Entity entity = new Entity("Users", KeyFactory.createKey("User", username));
entity.setProperty("email", email);

// Create a query that matches the email property
Query q = new Query();
q.setFilter(new FilterPredicate("email", FilterOperator.EQUAL, email));

// Start a transaction
Transaction txn = datastore.beginTransaction();

try {
    // Try to get an entity with that username
    datastore.get(KeyFactory.createKey("User", username);
}
catch(EntityNotFoundException e) {
    // No entity with that username, make sure the e-mail
    // is not taken either
    PreparedQuery pq = datastore.prepare(q);
    if (pq.countEntities(FetchOptions.Builder.withLimit(1)) == 0) {
        // The e-mail isn't taken either, all good
        datastore.put(entity);
        txn.commit();

        ... handle success here ...

        return;
    }
}
finally {
    if (txn.isActive())
        txn.rollback();
}

... handle failure here ...

But after a few simple tests I noticed that the query doesn't always "see" the "puts" that were made a short time before (eventual consistency, I should have guessed). To try to work around that, I tried to turn that query into a "dummy" ancestor query.

So this "dummy" ancestor query works like this. I create an entity of kind RootUser with a named key. I get the key from this root user, and make it the ancestor key for the query in the code above. Now that didn't work either, I'm still getting duplicate e-mail addresses. I also tried to configure the transaction so it's a cross-group transaction, but that didn't help either.

So, any tips on how to get this working? Is it possible at all?

Upvotes: 3

Views: 468

Answers (1)

lecstor
lecstor

Reputation: 5707

quick version: You want to use another entity "kind" and store your unique values as the id for objects in this kind. eg. "User.username:myusername", "User.email:[email protected]". By creating these entries first, you'll know when a value isn't unique.

see this for an example implementation.. http://webapp-improved.appspot.com/_modules/webapp2_extras/appengine/auth/models.html#Unique

Upvotes: 2

Related Questions