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:
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?
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?
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
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; }
}
}
}
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>
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With