Reputation: 17064
I have such entities persisted in RavenDB:
public class Project: RootAggregate
{
public string Name { get; set; }
}
public class Profile: RootAggregate
{
public string User { get; set; }
public IList<Project> FavoriteProjects { get; set; }
}
Since, it is document database, not relational one, it is denormalized and it supposed to be ok. Anyway, I didn't designed it this way:
public class Profile: RootAggregate
{
public string User { get; set; }
public IList<string> FavoriteProjectsIds { get; set; }
}
But sometimes I have to load up-to-dated projects (for this case with up-to-dated names). I've read on Ayende blog that can use Include()
, like below:
var profile = session.Include<Project>(x => x.Id)
.Load<Profile>(Id);
firstly I thought that the statement:
var projects = profile.FavouriteProjects;
will give me merged up-to-dated results, but no. I was confused, but then I've read further, and realized that it is ok, but the query below is supposed not to reach the database second time (as far as I understand Ayende's post):
var projectsIds = profile.FavoriteProjects.Select(x => x.Id).ToList();
var projects = session.Load<Project>(projectsIds);
Am I right? I'm asking because in the blog there was speech about single entity (Customer
), while I want to retrieve whole up-to-dated collection (IList<Project>
).
Next, what if while getting fresh projects connected with the profile, I want to search the same time along indexed profiles (because I have no theirs IDs), for example like below (such construction is not allowed):
var profile = session.Include<Project>(x => x.Id)
.Query<Profile>()
.Single(x => x.User == "jwa");
Can it be done some way, or need I to retrieve up-to-dated projects "by myself" like below ?
var profile = session.Query<Profile>().Single(x = x.User == "jwa");
var projectsIds = profile.FavoriteProjects.Select(x => x.Id).ToList();
var projects = session.Load<Project>(projectsIds);
Shall it be redesigned to be more relational ?
Upvotes: 1
Views: 422
Reputation: 6839
I'm sorry but it is hard for me to follow your question, however, I'll try to give you a more general answer for your scenario.
The purpose of the includes features is to eliminate the need for subsequent requests to the database in order to fetch documents of which you get the id in an initial request. Let's say you have a model like this:
public class Project
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Profile
{
public string Id { get; set; }
public string Username { get; set; }
public IList<string> FavoriteProjectIds { get; set; }
}
Now, if you want to load a profile you can do this:
var profile = session.Load<Profile>("profiles/daniel");
This will give you the profile document which contains the ids of all favorite projects of this user. Let's say you want to display a list of the project names. You will do something like this:
var favoriteProjects = session.Load<Project>(profile.FavoriteProjectIds);
This works just fine, however, it produces two requests to the database. If you're running in embedded mode, this doesn't matter for you, but if you have a remote ravendb server, you probably want to eliminate the second http request as you don't need it anyway. The .Include<T>()
feature comes in handy:
var profile = session.Include<Profile>(x => x.FavoriteProjectIds)
.Load("profiles/daniel");
var favoriteProjects = session.Load<Project>(profile.FavoriteProjectIds);
This code will go to the database only once and that's why it is faster. Notice how we specified the the type paramater on the Include call - this is only because we want to have support for lambda expressions in the parameter and has nothing to do with the type of the included document.
This is how it works using Include. I recommend to go with this approach for the sake of simplicity. If you really want to get the ultimate maximum of performance or you have other reasons for not going with this approach (e.g. sharded environment where profiles and projects don't reside on the same shard) you can denormalize some parts of the projects into your profiles.
The important thing to note here - you never want to denormalize the whole project document inside the profile document. Let's say you need to display a user's profile page somewhere in your application. On this page, you also want to display a list of the users favorite projects. What do you really need to display that list?
In this case, you only need to denormalize the projects names in order to construct a proper html-link (you have the ids anyway). Speaking in code, you might have a class that looks like this:
public class DenormalizedProject
{
public string Id { get; set; }
public string Name { get; set; }
}
... and your profile class would contain a property like this...
public IList<DenormalizedProject> FavoriteProjects { get; set; }
Using this approach, you have everything you need to display your profile page in one single document and you don't even need to use Include in order to fetch that information. However, this comes with a cost - you now have to maintain the denormalized project name. If it is immutable, fine, but in most cases the user can change it and then you have to make sure all the denormalized references get updated as well (using a batch command or just loading and updating all the containing documents). This is something you definitely want to take into consideration before going after the denormalization approach.
Upvotes: 2