Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guid as Id in RavenDB

Tags:

ravendb

The RavenDb documentation states:

Numeric or Guid Id properties are supported and will work seamlessly. In this case, RavenDB will automatically make the translation between the inner string ID to the numeric or Guid value shown in the entity and back.

I have stored the following objects:

class A
{
    public Guid Id { get; set; }
    public Guid BId { get; set; }
}

class B
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

I have then created the following projection:

class AB
{
    public Guid Id { get; set; }       // This should be the Id of A
    public Guid BId { get; set; }      // This should be the Id of B
    public string BName { get; set; }  // This should be the name of B
}

I have created the following index to create the projection:

class MyIndex : AbstractIndexCreationTask<AB>
{
    public MyIndex()
    {
        Map = docs =>
            from d in docs
            select new
            {
                d.Id,
                d.BId,
                BName = string.Empty
            };

        TransformResults = (database, results) =>
            from r in results
            let b = database.Load<B>("bs/" + r.BId.ToString())
            select new
            {
                r.Id,
                r.BId,
                BName = b.Name
            };
    }
}

When I use the following query:

session.Query<AB, MyIndex>().FirstOrDefault(t => t.Id == guid);

I get this exception:

Error converting value "bs/cc0a65ae-dd36-4437-8a57-fa20b91eeef7" to type 'System.Guid'. Path 'Id'.

Questions:

  1. It is caused by the conversion in my projection since the Id is a string there and not my Guid anymore. However, leaving it out will not return the Id. What must I do?

  2. I have to use the string building "bs/" + r.BId.ToString() to load the related doc. Is there a way not having to do this? Is there some sort of function that would resolve the doc tag for me?

  3. Is there a generic way to strip out the document tag altogether?

My constraints.

I will generate the Guid and cannot let RavenDb generate it for me. I know that the Document ID in reality is string, but I really need to use a Guid that I create. I would prefer to own the Id property of my entities.

I'm using Raven.Client 1.0.972

like image 413
Mikael Östberg Avatar asked Oct 01 '12 23:10

Mikael Östberg


3 Answers

You can achieve this using a MultiMap/Reduce Index, but you will need some hackery:

1) You will need to reduce using strings, not guids. You can still get the values back as guids in your AB class, as I will demonstrate below.

2) You can't call the first property of your AB class "Id", as raven will try to translate it to "__document_id". So call it "AId" and it will work fine.

3) In the mapping phase, you have to manipulate the strings yourself to strip off the document key prefix.

Here's a sample program that puts it all together. This demonstrates that it does indeed work, but I think it also shows why Ayende prefers string identifiers so you don't have to deal with this kind of mess.

using System;
using System.Linq;
using Raven.Client.Document;
using Raven.Client.Indexes;

namespace RavenScratchTest
{
  class Program
  {
    static void Main()
    {
      var documentStore = new DocumentStore { Url = "http://localhost:8080" };
      documentStore.Initialize();
      IndexCreation.CreateIndexes(typeof(Program).Assembly, documentStore);

      using (var session = documentStore.OpenSession())
      {
        var b = new B { Id = Guid.NewGuid(), Name = "Foo" };
        var a = new A { Id = Guid.NewGuid(), BId = b.Id };

        session.Store(a);
        session.Store(b);

        session.SaveChanges();
      }

      using (var session = documentStore.OpenSession())
      {
        var a = session.Query<A>().Customize(x => x.WaitForNonStaleResults()).First();
        var b = session.Query<B>().Customize(x => x.WaitForNonStaleResults()).First();

        Console.WriteLine("A:  Id = {0}", a.Id);
        Console.WriteLine("   BId = {0}", a.BId);
        Console.WriteLine();
        Console.WriteLine("B:  Id = {0}", b.Id);
        Console.WriteLine("  Name = {0}", b.Name);
        Console.WriteLine();

        var guid = a.Id;
        var ab = session.Query<AB, MyIndex>().Customize(x => x.WaitForNonStaleResults())
          .FirstOrDefault(t => t.AId == guid);

        if (ab == null)
          Console.WriteLine("AB: NULL");
        else
        {
          Console.WriteLine("AB:  AId = {0}", ab.AId);
          Console.WriteLine("   BId = {0}", ab.BId);
          Console.WriteLine("   BName = {0}", ab.BName);
          Console.WriteLine();
        }
      }

      Console.WriteLine();
      Console.WriteLine("Done.");
      Console.ReadLine();
    }
  }

  class A
  {
    public Guid Id { get; set; }
    public Guid BId { get; set; }
  }

  class B
  {
    public Guid Id { get; set; }
    public string Name { get; set; }
  }

  class AB
  {
    public Guid AId { get; set; }
    public Guid BId { get; set; }
    public string BName { get; set; }
  }

  class MyIndex : AbstractMultiMapIndexCreationTask<MyIndex.ReduceResult>
  {
    public MyIndex()
    {
      AddMap<A>(docs => from a in docs
                select new
                {
                  AId = a.Id.ToString().Split('/')[1],
                  a.BId,
                  BName = (string)null
                });

      AddMap<B>(docs => from b in docs
                select new
                {
                  AId = (string)null,
                  BId = b.Id.ToString().Split('/')[1],
                  BName = b.Name
                });

      Reduce = results => from result in results
                group result by result.BId
                into g
                select new
                  {
                    g.FirstOrDefault(x => x.AId != null).AId,
                    BId = g.Key,
                    g.FirstOrDefault(x => x.BName != null).BName
                  };
    }

    internal class ReduceResult
    {
      public string AId { get; set; }
      public string BId { get; set; }
      public string BName { get; set; }
    }
  }
}
like image 151
Matt Johnson-Pint Avatar answered Nov 20 '22 06:11

Matt Johnson-Pint


You can provide an ID to RavenDB explicitly upon saving:

session.Store(doc, explicitIdValueString);

The explicitIdValueString can be a Guid string. This value will be used to identify the document within the entire database and will not be prefixed by a type tag name. You can also customized the tag name, or the ID generation strategy all together by overriding conventions on IDocumentStore.Conventions such as FindTypeTagName which is a Func<Type, string>.

like image 43
eulerfx Avatar answered Nov 20 '22 06:11

eulerfx


The main problem is that while RavenDB can deal with numeric / integer on the client, but on the server side, RavenDB uses string ids.

In general, it isn't recommended to use Guids / numeric ids.

like image 2
Ayende Rahien Avatar answered Nov 20 '22 05:11

Ayende Rahien