Querying for a base class

62 views
Skip to first unread message

Tobi

unread,
Jun 22, 2010, 10:11:17 AM6/22/10
to ravendb
Hi!

Consider having a base class Foo and two derived clases Bar and Baz.
I now store instances of Bar and Baz in RavenDB and can query Bar and
Baz docs.

Now I would like to make RavenDB give me all Foo docs (= Bar AND Baz).
Is there any indexing "trick" that would make this possible?

Raven doesn't record the base class CLR types, so a normal query
wouldn't find any Foo documents at all.

Tobias

Matt Warren

unread,
Jun 23, 2010, 5:07:51 AM6/23/10
to ravendb
I don't know if it's applicable to your situation, but I've spent some
timing implementing StackOverflow in RavenDB and it has a similar
situation. See this blog post for the full details
http://thelonedeveloper.blogspot.com/2010/05/stackoverflow-in-ravendb-question-and.html.

Basically in the StackOverflow database everything is a Post, but in
reality it's a Question or an Answer. There is a field called
"PostTypeId" the lets you know which it is (0 = question, 1 = answer).
Both questions and answer have common fields (i.e. in the Post base
class, but them have some that aren't shared. For instance a Answer
has a "ParentPostId", but a question doesn't, a Question has a "Title"
but an Answer doesn't etc.

To handle this in raven I created the following index (called
"PostType"):
Map = docs => from doc in docs
select new { PostTypeId = doc.PostTypeId },

The to query for "Answers" you can do the following:
session.Query<Answer>("PostType").Where(x => x.PostTypeId ==1)
or "Questions"
session.Query<Question>("PostType").Where(x => x.PostTypeId ==2)

The in code I have a base class called "Post", and the classes
"Question" and "Answer" inherit from it and add in the fields/
properties that are particular to them.

Tobi

unread,
Jun 23, 2010, 6:31:24 AM6/23/10
to rav...@googlegroups.com
Am 23.06.2010 11:07, schrieb Matt Warren:

> I don't know if it's applicable to your situation, but I've spent some
> timing implementing StackOverflow in RavenDB and it has a similar
> situation. See this blog post for the full details
> http://thelonedeveloper.blogspot.com/2010/05/stackoverflow-in-ravendb-question-and.html.

Nice reading, thanks!

But I'm not sure, how this Post/Question/Answer use case can work at all.

You are querying questions and answers with:

session.Query<Question>("PostIndex").Where(x => x. PostTypeId == 1)
session.Query<Answer>("PostIndex").Where(x => x. PostTypeId == 2)

If Question and Answer are classes derived from a class Post, then I
don't see, how this can work. If PostIndex looks like this:

documentStore.DatabaseCommands.PutIndex("PostIndex", new
IndexDefinition<Post>
{


Map = docs => from doc in docs
select new { PostTypeId = doc.PostTypeId }
}

Then storing a question or an answer would create no index entries,
because the PostIndex only indexes Post docs.

Tobias

Tobi

unread,
Jun 23, 2010, 7:54:07 AM6/23/10
to rav...@googlegroups.com
Am 23.06.2010 12:23, schrieb Ayende Rahien:

> Yes, you need to set the convention for FindTypeTagName.
>
> new DocumentConvention
> {
> FindTypeTagName = type => typeof(Foo).IsAssignableFrom(type)
> ? DocumentConvention.DefaultTypeTagName(typeof(Foo))
> : DocumentConvention.DefaultTypeTagName(type);
> }

Ok. But I'm not sure how to use this.

If I do:
documentStore.Conventions.FindTypeTagName = type =>
typeof(Foo).IsAssignableFrom(type) ?
DocumentConvention.DefaultTypeTagName(typeof(Foo)) :
DocumentConvention.DefaultTypeTagName(type);

Then I can indeed create and index on Foo and query all Bar and Baz
instances. But I can't query for Bar or Baz anymore, because then it
will try to load a Baz as a Bar ore vice versa.

Doing this:

var documentConvention = new DocumentConvention
{
FindTypeTagName = type => typeof(Foo).IsAssignableFrom(type)
? DocumentConvention.DefaultTypeTagName(typeof(Foo))
: DocumentConvention.DefaultTypeTagName(type)
};

documentStore.DatabaseCommands.PutIndex("Foo", new IndexDefinition<Foo>
{
Map = docs => from doc in docs select new { Value = doc.Value }
}.ToIndexDefinition(documentConvention));

...doesn't seem to work either. The FindTypeTagName is only evaluated
once during PutIndex and nothing else seems to change - this index
doesn't give me any results.

Tobias

Matt Warren

unread,
Jun 23, 2010, 7:54:43 AM6/23/10
to ravendb
Oh yeah I forgot to explain that bit and I missed out some other
details ;-)

Everything is stored as a Post which is a C# class that contains all
the fields that questions and answers can have.

The class hierarchy is then

BasePost
Question : BasePost
Answer : BasePost

So the idea is that you use Post (everything) for storing and indexing
and then Question/Answer for pulling out what you know is a Question
or Answer. You don't ever directly use BasePost as it's a bit
meaningless, but you can use Post if you want.

The JSON stuff actually makes a lot of this easier. It will try and
deserailize a RavenDB document into the type you give it (when you
call Load<Type>("")) and any fields that don't exist it will set to
default(T).

On Jun 23, 11:31 am, Tobi <listacco...@e-tobi.net> wrote:
> Am 23.06.2010 11:07, schrieb Matt Warren:
>
> > I don't know if it's applicable to your situation, but I've spent some
> > timing implementing StackOverflow in RavenDB and it has a similar
> > situation. See this blog post for the full details
> >http://thelonedeveloper.blogspot.com/2010/05/stackoverflow-in-ravendb....

Tobi

unread,
Jun 23, 2010, 8:40:01 AM6/23/10
to rav...@googlegroups.com
Am 23.06.2010 13:54, schrieb Matt Warren:

> Everything is stored as a Post which is a C# class that contains all
> the fields that questions and answers can have.
>
> The class hierarchy is then
>
> BasePost
> Question : BasePost
> Answer : BasePost

Ok. So this is basically:

public class Foo
{
public string Id { get; set; }
public string Type { get; set; }
public string FooValue { get; set; }
public string BarValue { get; set; }
public string BazValue { get; set; }
}

public class Bar : Foo
{
public Bar()
{
Type = "Bar";
}
}

public class Baz : Foo
{
public Baz()
{
Type = "Baz";
}
}

And I create this index:

documentStore.DatabaseCommands.PutIndex("Foo", new IndexDefinition<Foo>
{

Map = docs => from doc in docs select new {Type = doc.Type}
});

And I store the docs as Foos:

session.Store(new Foo {Type = "Bar", ...});
session.Store(new Foo {Type = "Baz", ...});

And when I query it this way;

session.Query<Bar>("Foo").Where(x => x.Type == "Bar")

... I will get a casting exception, because it tries to cast a Foo to
Bar. I think, I'm still missing something from your solution.


I can get it woking, if I globally set this document convention:

documentStore.Conventions.FindTypeTagName =
type => typeof (Foo).IsAssignableFrom(type)
? DocumentConvention.DefaultTypeTagName(typeof (Foo))
: DocumentConvention.DefaultTypeTagName(type);

And then I can even move the properties back to Bar and Baz again and
store Bar and Baz directly:

public class Foo
{
public string Id { get; set; }
public string Type { get; set; }
public string FooValue { get; set; }
}

public class Bar : Foo
{
public Bar()
{
Type = "Bar";
}
public string BarValue { get; set; }
}

public class Baz : Foo
{
public Baz()
{
Type = "Baz";
}
public string BazValue { get; set; }
}

session.Store(new Bar {FooValue = "fooValue", BarValue = "barValue"});
session.Store(new Baz {FooValue = "fooValue", BazValue = "bazValue"});
session.Query<Bar>("Foo").Where(x => x.Type == "Bar")

The Type property and it's index are still required. But I can even do
(which was my original question):

session.Query<Foo>("Foo")

...which returns a list of Bar and Baz instances.

If I know could get rid of the Type property somehow...

Tobias


Matt Warren

unread,
Jun 23, 2010, 9:04:56 AM6/23/10
to ravendb
Okay, it's been a while since I wrote the code, I'll need to take a
look at it again (if I can still find it). But this method did work
I've just forgotten some details.

Ayende Rahien

unread,
Jun 23, 2010, 12:14:42 PM6/23/10
to rav...@googlegroups.com
Tobi,
I am not sure what you are attempting to do.
Each document has a single entity tag, which you can use to limit searches.
If you want to do a cross tag search, you can use:

from doc in docs
let tag =  doc["@metadata"]["Raven-Entity-Name"]
where tag == "Foo" || tag == "Bar"
select new { doc.Name}

Tobi

unread,
Jun 23, 2010, 1:21:57 PM6/23/10
to rav...@googlegroups.com
Am 23.06.2010 18:14, schrieb Ayende Rahien:

> I am not sure what you are attempting to do.

What I would like to do is:

abstract class Foo { ... }
class Bar : Foo { ... }
class Baz : Foo { ... }

session.Store(new Bar {...});
session.Store(new Baz {...});

session.Query<Bar>("IndexA").Where(...)
session.Query<Baz>("IndexB").Where(...)

// This one should return an IEnumerabl<Foo> containing a Bar and
// a Baz instance:
session.Query<Foo>("IndexC").Where(...)

> Each document has a single entity tag, which you can use to limit searches.
> If you want to do a cross tag search, you can use:
>
> from doc in docs
> let tag = doc["@metadata"]["Raven-Entity-Name"]
> where tag == "Foo" || tag == "Bar"
> select new { doc.Name}

Thanks, this indeed works! I've never looked beyond IndexDefinition<T>.

Tobias

Reply all
Reply to author
Forward
0 new messages