RavenDB Transformations and Joining Simple Data

105 views
Skip to first unread message

jamesamuir

unread,
May 2, 2012, 5:43:25 PM5/2/12
to ravendb
I was wondering if someone could help me understand RavenDB
transformations as I cant seem to get them working correctly. I tried
following Ayende's post

http://ayende.com/blog/4661/ravendb-live-projections-or-how-to-do-joins-in-a-non-relational-database

but I am not able to apply it to my situation. I have two models, a
UserModel and an UserAddressModel. The code is as follows

public class UserModel
{
#region Properties

public string Id { get; set; }

public string AccountId { get; set; }

public string UserName { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public string Email { get; set; }

public DateTime InsertDate { get; set; }

#endregion
}
and

public class UserAddressModel
{

public string Id { get; set; }

public string AccountId { get; set; }

public string Address1 { get; set; }

public string Address2 { get; set; }

public string City { get; set; }

public string State { get; set; }

public string Zipcode { get; set; }

public float Latitude { get; set; }

public float Longitude { get; set; }

public DateTime InsertDate { get; set; }

}
I have my RavenDB Index set up like such.

public class UserDashboard_ByName :
AbstractIndexCreationTask<UserModel>
{
public UserDashboard_ByName()
{
Map = users => from user in users
select new { user.UserName,
user.AccountId };

TransformResults =
(database, users) => from user in users
let useraddress =
database.Load<UserAddressModel>(user.AccountId)
select new
{ FirstName =
user.FirstName,
LastName =
user.LastName,
Address1 =
useraddress.Address1,
Address2 =
useraddress.Address2

};
}
}
and I am calling it with the following code

using (var session = DataDocumentStore.Instance.OpenSession())
{

//Get the AccountId
var userDashboard =
(from user in session.Query<UserModel,
UserDashboard_ByName>()
where user.UserName == userName
select user).SingleOrDefault();


}
When I call the index, it returns a UserModel type, not the anonymous
type that I am expecting. Also, I do not understand how I can call

let useraddress = database.Load<UserAddressModel>(user.AccountId)
when there is no specific relationship that has been specified in code
or anywhere else.

When I view the index in the RavenDB studio, I am getting null for all
of the address fields as well.

Maybe someone can explain to me how I should be joining data in
RavenDB? Some expanded documentation or a nudge in the right direction
to understanding this better would be greatly appreciated. Thanks in
advance.

Itamar Syn-Hershko

unread,
May 2, 2012, 6:05:56 PM5/2/12
to rav...@googlegroups.com
You are trying to do a relational operation with RavenDB. That won't work.

You want to have all the user data in a User class, and if for some of your screens you only need his name and address, you can easily use a TransformResults function to filter it out - but for most use cases you don't even have to do that

Matt Warren

unread,
May 2, 2012, 6:17:31 PM5/2/12
to rav...@googlegroups.com
In reply to your other question:

Also, I do not understand how I can call

let useraddress = database.Load<UserAddressModel>(user.AccountId)
when there is no specific relationship that has been specified in code
or anywhere else.

This works because you are just asking RavenDB to load a doc using the given key (contained in the user.AccountId field), so it does. The code in your transform result is what sepcifies the relationship.

jamesamuir

unread,
May 2, 2012, 7:31:53 PM5/2/12
to ravendb
So am I correct in assuming that any relationships need to be stored
in each document? Therefore, if I wanted to associate multiple
addresses with my user the class should look like this?


public class UserModel
{
#region Properties
public string Id { get; set; }
public string AccountId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime InsertDate { get; set; }

public List<UserAddressModel> UserAddresses { get; set; }

#endregion
}

If this is the case, what exactly is the "let" statement used for and
how would I go about getting a list of all user addresses since each
is contained in a specific UserModel?




On May 2, 6:05 pm, Itamar Syn-Hershko <ita...@hibernatingrhinos.com>
wrote:
> You are trying to do a relational operation with RavenDB. That won't work.
>
> You want to have all the user data in a User class, and if for some of your
> screens you only need his name and address, you can easily use a
> TransformResults function to filter it out - but for most use cases you
> don't even have to do that
>
>
>
>
>
>
>
> On Thu, May 3, 2012 at 12:43 AM, jamesamuir <jamesam...@gmail.com> wrote:
> > I was wondering if someone could help me understand RavenDB
> > transformations as I cant seem to get them working correctly. I tried
> > following Ayende's post
>
> >http://ayende.com/blog/4661/ravendb-live-projections-or-how-to-do-joi...

Itamar Syn-Hershko

unread,
May 2, 2012, 7:34:51 PM5/2/12
to rav...@googlegroups.com
inline

On Thu, May 3, 2012 at 2:31 AM, jamesamuir <james...@gmail.com> wrote:
So am I correct in assuming that any relationships need to be stored
in each document? Therefore, if I wanted to associate multiple
addresses with my user the class should look like this?

Yes

You will have one User class (not sure why you name it UserModel) which will have a list of UserAddress objects, like you did. They are all within the same transactional boundary.
 


public class UserModel
   {
       #region Properties
       public string Id { get; set; }
       public string AccountId { get; set; }
       public string UserName { get; set; }
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public string Email { get; set; }
       public DateTime InsertDate { get; set; }

       public List<UserAddressModel> UserAddresses { get; set; }

       #endregion
   }

If this is the case, what exactly is the "let" statement used for and
how would I go about getting a list of all user addresses since each
is contained in a specific UserModel?

I'm not following. Just return the User object by Id or by any other queryable parameter and you'll have the list of address - the entire object will be returned to you

jamesamuir

unread,
May 2, 2012, 7:41:21 PM5/2/12
to ravendb
Sorry, I should have clarified that last statement. Hypothetically, I
wanted to return a list of all of the addresses across the spectrum of
users, similar to SELECT * FROM USERADDRESS. I guess this is what I am
having trouble understanding. If I was using a relational database,
the address table would be abstracted from the user and I would be
able to query against all addresses but still have the ability to
associate each address with a specific user. In RavenDB each address
is compartmentalized at the user level. Thank you for taking the time
to assist me with this.

On May 2, 7:34 pm, Itamar Syn-Hershko <ita...@hibernatingrhinos.com>
wrote:
> inline

Daniel Lang

unread,
May 2, 2012, 7:45:00 PM5/2/12
to rav...@googlegroups.com
If you really want to have a list of all addresses for some reason (it's actually hard to think about a good use-case, but let's say you want to have all addresses sorted alphabetically) then you can define a map index on those addresses and use fieldstorage to store the values inside the lucene index. Using this approach, you can actually get the results from the index itself.

jamesamuir

unread,
May 2, 2012, 8:08:56 PM5/2/12
to ravendb
Daniel,
I was thinking in a more general sense but used the address example as
it was already part of the discussion. If I move beyond the address
example to, lets say, a UserTask i think it would make more sense of
what i would like to accomplish.

public class UserTask
{
#region Properties
public string Id { get; set; }
public string AccountId { get; set; }
public string TaskDesc { get; set; }
public DateTime InsertDate { get; set; }
#endregion
}


If each UserTask (which could be many) as associated at the User
level, it seems difficult to obtain a list of all of the tasks. For
example, if I wanted to display a list of the newest tasks and the
user it is assigned to. Im not sure what the index would look like for
something like this if you could provide a simple example for me?

On May 2, 7:45 pm, Daniel Lang <d.l...@flexbit.at> wrote:
> If you really want to have a list of all addresses for *some* reason (it's

Itamar Syn-Hershko

unread,
May 2, 2012, 8:11:11 PM5/2/12
to rav...@googlegroups.com
Assigning a user to a task doesn't mean you actually are going to embed the task object within the user object

If you are going to be adding a lot of tasks and querying them like you mentioned, you will just store UserTask objects with a UserId property that will hold the User ID, and create an index on it

jamesamuir

unread,
May 2, 2012, 11:59:45 PM5/2/12
to ravendb
So here is the index I created to try this using the UserName as the
index


public class UserTasks_ByName : AbstractIndexCreationTask<UserTask>
    {
        public UserTasks_ByName()
        {
            Map = usertasks => from usertask in usertasks
                               select new { UserName =
usertask.UserName };




            TransformResults =
                (database, usertasks) => from usertask in usertasks
                                     let user =
database.Load<UserModel>(usertask.UserName)
                                     select new
                                     {
                                         FirstName = user.FirstName,
                                         LastName = user.LastName,
                                         TaskDesc = usertask.TaskDesc,
                                         InsertDate =
usertask.InsertDate




                                     };
        }
    }


and here is a result in RavenDB Studio for a that index

{
  "FirstName": null,
  "LastName": null,
  "TaskDesc": "New Task",
  "InsertDate": "2012-05-02T23:53:33.6165908"
}

Can you point out what it is that I am doing wrong in this instance?

On May 2, 8:11 pm, Itamar Syn-Hershko <ita...@hibernatingrhinos.com>
wrote:

Maverix

unread,
May 3, 2012, 12:16:07 AM5/3/12
to rav...@googlegroups.com
From my understanding what you are seeing is correct.
In the index those fields are not populated. 
 
TransformResults are run on demand (not sure if it is client side or server)

Matt Warren

unread,
May 3, 2012, 4:28:13 AM5/3/12
to rav...@googlegroups.com
TransformResults runs on the server, so in effect it modifies the Json that is sent out to the client.

Itamar Syn-Hershko

unread,
May 3, 2012, 4:28:36 AM5/3/12
to rav...@googlegroups.com
The problem is here:

let user = 
database.Load<UserModel>(usertask.UserName) 

You can't load a user based on a property - only based on the document ID, which is probably usertask.UserName

In this scenario you probably want to use Includes to load all task with their user documents: ravendb.net/docs/client-api/querying/handling-document-relationships

Matt Warren

unread,
May 3, 2012, 4:30:26 AM5/3/12
to rav...@googlegroups.com
It look like it's not loading the associated user correctly, can you post a sample showing what the UserTask doc looks like as Json. 

But, I think your TransformResult is wrong, try this:

 TransformResults =  (database, usertasks) => from usertask in usertasks 
                                     let user = database.Load<UserModel>(usertask.UserId

                                     select new 
                                     { 
                                         FirstName = user.FirstName, 
                                         LastName = user.LastName, 
                                         TaskDesc = usertask.TaskDesc, 
                                         InsertDate = usertask.InsertDate 
                                     };  

jamesamuir

unread,
May 3, 2012, 10:46:58 PM5/3/12
to ravendb
I managed to get the UserTask example working tonight and I think I'm
starting to understand how to model the data relationships a bit
better in RavenDB. I wish there was a bit more documentation out there
that explained how to handle data relationships beyond the ravendb.net/
docs/client-api/querying/handling-document-relationships post. I keep
reading it but it seems to be over the head of new RavenDB user. I
must say that this is my first foray into NoSQL and I like the way it
integrates into the .Net framework. I guess I'll just keep picking at
it and hope that a book comes out soon. Thank you to everyone that
helped me.

On May 3, 4:30 am, Matt Warren <mattd...@gmail.com> wrote:
> It look like it's not loading the associated user correctly, can you post a
> sample showing what the UserTask doc looks like as Json.
>
> But, I think your TransformResult is wrong, try this:
>
>  TransformResults =  (database, usertasks) => from usertask in usertasks
>                                      let user
> = database.Load<UserModel>(usertask.*UserId*)
> ...
>
> read more »

jamesamuir

unread,
May 3, 2012, 10:49:23 PM5/3/12
to ravendb
Sorry, I should have mentioned that Itamar and Matt's suggestions were
correct. I needed to use UserId instead of UserName in my index. All
of the result also appear as expected in Raven Studio.

On May 3, 4:30 am, Matt Warren <mattd...@gmail.com> wrote:
> It look like it's not loading the associated user correctly, can you post a
> sample showing what the UserTask doc looks like as Json.
>
> But, I think your TransformResult is wrong, try this:
>
>  TransformResults =  (database, usertasks) => from usertask in usertasks
>                                      let user
> = database.Load<UserModel>(usertask.*UserId*)
> ...
>
> read more »

Itamar Syn-Hershko

unread,
May 3, 2012, 11:04:48 PM5/3/12
to rav...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages