document ids are not generated for nested objects

992 views
Skip to first unread message

Daniel Steigerwald

unread,
Jun 27, 2010, 1:15:31 PM6/27/10
to ravendb
I am not sure if it's bug or feature, or something else :-)
I expected channels will have its ids auto assigned.
Here is code:

public class Channel
{
public string Id { get; set; }
public string Name { get; set; }
public List<Channel> Children { get; set; }
}

public class Organization
{
public string Id { get; set; }
public string Name { get; set; }
public List<Channel> Channels { get; set; }
}

void LoadFixtures()
{
using (var session = _documentStore.OpenSession())
{
var organization = session.Load<Organization>("organizations/1");
if (organization != null) return;

organization = new Organization
{
Id = "organizations/1",
Name = "test",
Channels = new List<Channel> {
new Channel { Name = "Something" },
new Channel { Name = "Anything" },
new Channel { Name = "Nothing" },
new Channel {
Name = "Thing",
Children = new List<Channel> {
new Channel { Name = "RedThing" }
}
},
new Channel { Name = "BlueThing" },
new Channel { Name = "EndThing" }
}
};
session.Store(organization);
session.SaveChanges();
}
}

Daniel Steigerwald

unread,
Jun 27, 2010, 1:20:27 PM6/27/10
to ravendb
Created json:
{
"Name": "test",
"Channels": [
{
"Id": null,
"Name": "Something",
"Children": null
},
{
"Id": null,
"Name": "Anything",
"Children": null
},
{
"Id": null,
"Name": "Nothing",
"Children": null
},
{
"Id": null,
"Name": "Thing",
"Children": [
{
"Id": null,
"Name": "RedThing",
"Children": null
}
]
},
{
"Id": null,
"Name": "BlueThing",
"Children": null
},
{
"Id": null,
"Name": "EndThing",
"Children": null
}
]
}

Have I save all channels separately to assign id?

Tobias Grimm

unread,
Jun 27, 2010, 1:41:33 PM6/27/10
to rav...@googlegroups.com
Am Sonntag, den 27.06.2010, 10:15 -0700 schrieb Daniel Steigerwald:

> I am not sure if it's bug or feature, or something else :-)
> I expected channels will have its ids auto assigned.

The channels are part of the Organization document.
Only aggregate roots which you save with session.Store() will get an Id
assigned.

Tobias


Daniel Steigerwald

unread,
Jun 27, 2010, 1:56:35 PM6/27/10
to ravendb
Obviously. But I do not see any advantage of it.

Tobias Grimm

unread,
Jun 27, 2010, 2:06:02 PM6/27/10
to rav...@googlegroups.com
Am Sonntag, den 27.06.2010, 10:56 -0700 schrieb Daniel Steigerwald:

> Obviously. But I do not see any advantage of it.

That's not a matter of being an advantage or not. Your store and load
Documents, so they need an Id.

In your sample you load and store Organization instances, not Channel
instances. A Channel can't live outside of an Organization. If it could,
a Channel would be a document of it's own.

Tobias


Ayende Rahien

unread,
Jun 27, 2010, 2:36:39 PM6/27/10
to rav...@googlegroups.com
Daniel,
That is expected, only the root entity gets an id. All the other objects are considered to be value types, with no intrinsic identity of their own

Daniel Steigerwald

unread,
Jun 27, 2010, 4:30:40 PM6/27/10
to ravendb
I just reread http://ravendb.net/documentation/docs-document-design.
Well, it makes sense.
Still, session.Conventions.GraphIdentity = true option would be
fine :-)

Lee Treveil

unread,
Jun 27, 2010, 4:51:27 PM6/27/10
to rav...@googlegroups.com
You could just Conventions.GenerateDocumentKey() to generate the id's manually.

Ayende Rahien

unread,
Jun 28, 2010, 2:27:39 AM6/28/10
to rav...@googlegroups.com
What would you expect this to do?

Daniel Steigerwald

unread,
Jun 28, 2010, 8:00:41 AM6/28/10
to ravendb
It should just assign id not only at stored document, but also for all
nested documents.
Document should be considered as anything which has Id property.
It would allow to write such code:

session.Store(new Organization
{
Id = "organizations/1",
Name = "The First",


Channels = new List<Channel>
{

new Channel { Name = "a" },
new Channel { Name = "b" },
new Channel { Name = "c" },
new Channel {
Name = "d",


Children = new List<Channel> {

new Channel { Name = "e" }
}
},
new Channel { Name = "f" },
new Channel { Name = "h" }
}
});

Now, we have to store each channel separately.

On 28 čvn, 08:27, Ayende Rahien <aye...@ayende.com> wrote:
> What would you expect this to do?
>
> On Sun, Jun 27, 2010 at 11:30 PM, Daniel Steigerwald
> <dan...@steigerwald.cz>wrote:
>
>
>

> > I just rereadhttp://ravendb.net/documentation/docs-document-design.

Daniel Steigerwald

unread,
Jun 28, 2010, 8:28:37 AM6/28/10
to ravendb
Now I am using this appraoch

Func<Channel, Channel> store = delegate(Channel channel)
{
session.Store(channel);
return channel;
};
session.Store(new Organization
{
Id = "organizations/1",
Name = "The First",
Channels = new List<Channel> {
store(new Channel { Name = "a" }),
store(new Channel { Name = "b" }),
store(new Channel { Name = "c" }),
store(new Channel {
Name = "d",
Children = new List<Channel> {
store(new Channel { Name = "e" })
}
}),
store(new Channel { Name = "f" }),
store(new Channel { Name = "g" })
}
});

Tobi

unread,
Jun 28, 2010, 8:30:31 AM6/28/10
to rav...@googlegroups.com
Am 28.06.2010 14:00, schrieb Daniel Steigerwald:

> It should just assign id not only at stored document, but also for all
> nested documents.

There is no such thing as "nested documents". What you are aiming at
is probably more something like an ORDBMS.

Tobias

Tobi

unread,
Jun 28, 2010, 8:38:22 AM6/28/10
to rav...@googlegroups.com

What exactly are you trying to do?

With your sample above, keep in mind, that changing a Channel document
will not change the channel item in the Organization document.

Relations between documents are not supported by RavenDB (no joining,
lazy loading or whaever). You've to do this on the client side and then
you would probably only store the channel-Id's in the Organization
document.

Tobias

Daniel Steigerwald

unread,
Jun 28, 2010, 8:43:14 AM6/28/10
to ravendb
Tobi. Thank you for all your comments, I appreciate it. But, can you
model/organize it better? NOSQL uses references a lot, as I have seen
in many spots. The difference is, these references are not enforced.
Still, they are references. Therefore, term nested document is valid.

Ryan Heath

unread,
Jun 28, 2010, 8:47:59 AM6/28/10
to rav...@googlegroups.com
But then again why do you need a channelID?
In your model (as currently displayed): without an Organization id a
channel instance has no meaning.

Do you need to share channels between organisations?

// Ryan

Daniel Steigerwald

unread,
Jun 28, 2010, 8:50:56 AM6/28/10
to ravendb
Tobi, you are not telling me anything what I would not know. Surely
they are separated. Surely the change of one of organization document
will not have any impact. I suppose we all know it. We model our data
to "how it will be used". Maybe you were confused, because I used
Channel class instance, instead of new { id: .., name: ''}; But I do
not reason to not reuse existing class which has exactly these
properties which I need :-)

Take a look: http://ayende.com/Blog/archive/2010/05/20/porting-mvc-music-store-to-raven-data-migration.aspx
Do you see how album has Genre, and Genre is actually stored on two
places (for one album)?
I used exactly same approach.

Daniel Steigerwald

unread,
Jun 28, 2010, 8:56:29 AM6/28/10
to ravendb
I need channel id for these reasons:
1) localization, Id will be used in string resources.
2) url - Names in url are bad, since names can be (and will be)
changed
3) Model contains - messages, which has to have channelId
Ids should be immutable.

No, channels will not be shared.

Ayende Rahien

unread,
Jun 28, 2010, 9:06:50 AM6/28/10
to rav...@googlegroups.com
This does something drastically different (and wrong, probably).
It create the documents as channels, as well as clone them.

Basically, you seem to have a problem distinguishing between root aggregates and references.
I suggest reading this:

Raven (by design) doesn't support references between objects.

Tobi

unread,
Jun 28, 2010, 9:14:33 AM6/28/10
to rav...@googlegroups.com
Am 28.06.2010 14:50, schrieb Daniel Steigerwald:

> Tobi, you are not telling me anything what I would not know. Surely
> they are separated. Surely the change of one of organization document
> will not have any impact. I suppose we all know it. We model our data
> to "how it will be used".


This is the missig part for me. You haven't showed how you intend to
use the Channel and Organization classes once you have stored them.


> Maybe you were confused, because I used
> Channel class instance, instead of new { id: .., name: ''}; But I do
> not reason to not reuse existing class which has exactly these
> properties which I need :-)

That's perfectly fine. The question is, what does the channel need an
Id for? Within the Organiztation class Channel is a value object and
just doesn't need an Id.

If you intend to query for Channels, then a channel probably becomes an
Entity. Then it might be appropriate to store a list of channel id's
within Organization, which would introduce some kind of reference that
RavenDB does not support.

If RavenDB would support document references, one consequence would be,
that querying/loading one document must load a bunch of other,
referenced documents. This would introduces "SELECT N+1" problems
probably require a lazy loading solution cascaded deletes not to
mention the impact on sharding etc.

But RavenDB just is a simple document store. There are no relations,
because it's not a relational database.

So if you want to have one document to relate to another one, you have
to handle this on the client side.

Tobias

Daniel Steigerwald

unread,
Jun 28, 2010, 10:38:49 AM6/28/10
to ravendb
Organization with its channels is used for UI. You described it
exactly how I intended to use it :)
I want to get organization with its channels for one request, then
send as JSON to the browser, then render it.
Also, the tree structure is maintained by organization itself. Not by
channels.
I am building message system. So each message have to have a reference
to some channel.
Btw, I am very happy we can discuss such issues.

Daniel Steigerwald

unread,
Jun 28, 2010, 10:46:30 AM6/28/10
to ravendb
We can talk hours about principles. Code matters. Cloning is intended.
Organization hold structure/tree of channels. Channels are stored also
itself as document. I need its ids for messages. I can not store
messages directly into channel.

PS: I am still newbie and learning things. Hence, one line of code is
better than ten words for me :-)

Ryan Heath

unread,
Jun 28, 2010, 10:58:32 AM6/28/10
to rav...@googlegroups.com
Why not give the channel an sequence id when added to the organisation?

var chanX = org1.AddChannel("channel x"); // gets id 0
var chanY = org1.AddChannel("channel y");// gets id 1
var chanZ = chanY.AddChannel("channel z"); // gets id 2

var chanA = org2.AddChannel("channel a");// gets id 0
var chanB = org2.AddChannel("channel b"); // gets id 1

Again, they are sequence ids, they only have a meaning with an organization id.

A channel id could be seen as orgID + "/" + channelSequendID.
This channel id could be store with the messages. (the orgID with the
channelSeqID)

HTH
// Ryan

On Mon, Jun 28, 2010 at 4:46 PM, Daniel Steigerwald

Tobi

unread,
Jun 28, 2010, 10:59:40 AM6/28/10
to rav...@googlegroups.com
Am 28.06.2010 16:38, schrieb Daniel Steigerwald:

> I want to get organization with its channels for one request, then
> send as JSON to the browser, then render it.
> Also, the tree structure is maintained by organization itself. Not by
> channels.
> I am building message system. So each message have to have a reference
> to some channel.

Please give some more details, how you want to use the
Channel.Id-Property in this use case.

If your are sending messages across channels, the sender and receiver
must somehow hook to the same channel. If no organizations share the
same channel, I see no need to make Channel a top-level entity. Still
each channel within an Organization would probably need a unique
identifier, which probably is, what you want. But I would simply use
the "channel name" for this.

Tobias

Daniel Steigerwald

unread,
Jun 28, 2010, 11:16:57 AM6/28/10
to ravendb
Yeah, I was thinking about the exactly same approach. I decided to let
raven generate id, even if I can generate it by my self. But you are
right, this is probably better. Thank you :-)

Daniel Steigerwald

unread,
Jun 28, 2010, 11:24:53 AM6/28/10
to ravendb
Tobias. I almost agree. I will use approach suggested by Ryan.
Except one thing. I would never ever use Channel Name for identity of
channel. Names tends to be changed. Ids should be immutable. There are
consequences, if for example I would use channel name in url, and
someone decided to rename channel. What should I do for other users?
They app instances would stop working. My application is pure ajax.
Something similar like http://code.quirkey.com/sammy/, but I use own
routing, templating etc.

Again, thank you guys.

PS: One reason why I wanted unique id (generated by ravendb) for
channels is concurrency. Imagine several peoples changing channels
tree. Sequential id needs some kind of singleton. GUID would be ideal,
but they are long.

Daniel Steigerwald

unread,
Jun 28, 2010, 2:21:03 PM6/28/10
to ravendb
Done, here is the code (maybe it could help someone)
Thank you Ryan.

public class Organization
{
readonly ICollection<Channel> channels;
public string Id { get; set; }
public string Name { get; set; }

public IEnumerable<Channel> Channels
{
get { return channels; }
}

public Organization()
{
channels = new List<Channel>();
}

public Channel AddChannel(Channel channel, ICollection<Channel>
channels = null)
{
channel.Organization = this;
(channels != null ? channels : this.channels).Add(channel);
if (channel.Id == null) channel.Id =
GetCount(this.channels).ToString();
return channel;
}

public int GetCount(IEnumerable<Channel> channels)
{
var id = 0;
foreach (var channel in channels)
id += 1 + GetCount(channel.Children);
return id;
}
}

public class Channel
{
readonly ICollection<Channel> children;
public string Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public Organization Organization { get; set; }
public IEnumerable<Channel> Children
{
get { return children; }
}

public Channel() {
children = new List<Channel>();
}

public void AddChannel(Channel channel)
{
if (Organization == null) {
throw new ApplicationException("channel has to have organization");
}
Organization.AddChannel(channel, children);
}
}

On 28 čvn, 16:58, Ryan Heath <ryan.q.he...@gmail.com> wrote:

Ayende Rahien

unread,
Jun 29, 2010, 1:17:02 AM6/29/10
to rav...@googlegroups.com
Daniel,
A channel, when embedded into an organization, is a value object.
There is no such thing as channels/3
What you can do is to give a unique id for the channel inside the organization and then say:
Channel #3 in organizations/4

Ayende Rahien

unread,
Jun 29, 2010, 1:18:28 AM6/29/10
to rav...@googlegroups.com
Daniel,
Since channel is local to an organization, that doesn't matter.
You can use etags to ensure that you never overwrite things, and that allow you to use sequence numbers.

Ryan Heath

unread,
Jun 29, 2010, 4:05:21 AM6/29/10
to rav...@googlegroups.com
I would not use count, but a number that is kept by a organisation instance.
It will always increment when you add a channel.

// Ryan

On Mon, Jun 28, 2010 at 8:21 PM, Daniel Steigerwald

Daniel Steigerwald

unread,
Jun 29, 2010, 7:13:30 AM6/29/10
to ravendb
Yeah, it makes sense :-) Now I can remove resursion :)
I admire code reviews. Best way to learn new things.
Thank you.
Reply all
Reply to author
Forward
0 new messages