2
votes

What is the best way of using URLs as keys in RavenDB?

Unfortunately, the semantics of updating an item are not clear in the docs: if the key ends in backslash, it is always an insert, otherwise it could be an update if the key already exists.

But URLs can end up in slash, while RavenDB uses terminating slashes for key generation

RavenDB also supports the notion of Identity, for example if you need IDs to be consecutive. By creating a string Id property in your entity, and setting it to a value ending with a slash (/), you can tell RavenDB to use that as a key prefix for your entity. That prefix followed by the next available integer ID for it will be your entity's ID after you call SaveChanges(). and that forward slash cannot be reconfigured. So it does not really "support" it, but enforce it.

Edit: The same RavenDB documentation page states the following, which does not correspond to the observed behaviour:

You are able to assign to a document any ID as you can imagine. Everything is going to work correctly however you have to be aware that some kind of IDs might cause performance issues when number of documents with custom generated IDs is very high (millions of documents).

The options are: 1. Modify your urls to remove ending slash 2. Url encode the urls (as suggested by RavenDB maintainers) 3. Modify the schema and use database-generated IDs

Option 1 should be safe in most cases. Option 2 would make the keys unreadable, plus I have not managed to make it work (might be a bug in either RavenDB or my code). Option 3 seems to unnecessarily complicate the schema.

What would be the best course of action, in general?

3

3 Answers

3
votes

It is a matter of preference I suppose - but I don't think URLs make very good keys. Others disagree. See this discussion on the support forum.

I would think hard about why you need it as a key, and if you could index the url instead. For example:

public class Site
{
    public string Id { get; set; } // such as "sites/1"
    public string Name { get; set; }
    public string Url { get; set; }
}

var q = session.Query<Site>().Where(x=> x.Url == "http://foo/bar");

The argument for using it as the key is that a URL is supposed to uniquely represent a resource, but this isn't always the case when you consider http vs. https, optional querystring parameters, etc.

Also, RavenDB document keys are case insensitive, while URLs are (in general) case sensitive. Many web servers will ignore case on the base url, but it's still up to the web application to decide how to be case sensitive or insensitive on the querystring parameters. So it's entirely possible that http://foo/bar?q=abc and http://foo/bar?q=ABC refer to two separate resources, but they would be treated as the same document key in Raven.

If you feel you must use URLs as document keys, then as Ayende stated, you should escape them somehow, probably like this:

// to escape
var key = Uri.EscapeDataString(url);


// to unescape
var url = Uri.UnescapeDataString(key);

I'm sure there are other escaping or encoding formats that would work as well, this just seems to be the easiest one.

1
votes

You need to escape the URL if you want to use it as a document key.

1
votes

Let me chime in here, from a practitioners' perspective. I have to store OpenIds as document keys in RavenDb and so naturally this issue came up. While the recent builds have fixed problems relating to esaceped URLs as Document Keys, it's still not something I can recommend as I have hit various bugs with it. I reported some of them and they got fixed, but I have given up eventually.

I first started out using base64 encoding on the URLs but quickly discovered that this may lead to long document keys that exceed RavenDb limits and you'll see server-side Esent ColumnTooBig Exceptions.

Since these Ids were getting too long (and legal URLs may be up to 2000 characters) I have started using base64 encoded SHA-Hashes of them. Can't say it's elegant, but it works pretty well.