使用RavenDB中的Includes()处理非规范化,以检索最新数据

时间:2012-05-13 17:09:06

标签: .net ravendb

我在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; }
}

因为它是文档数据库,而不是关系数据库,它是非规范化的,它应该没问题。无论如何,我没有这样设计:

public class Profile: RootAggregate
{
    public string User { get; set; }
    public IList<string> FavoriteProjectsIds { get; set; }
}

但有时我必须加载最新的项目(对于这种情况,使用最新的名称)。我读过Ayende blog可以使用Include(),如下所示:

var profile = session.Include<Project>(x => x.Id)
                     .Load<Profile>(Id);

首先我认为声明:

var projects = profile.FavouriteProjects;

会给我合并的最新结果,但没有。我很困惑,但后来我进一步阅读,并意识到它没关系,但下面的查询应该不会第二次到达数据库(据我所知Ayende的帖子):

var projectsIds = profile.FavoriteProjects.Select(x => x.Id).ToList();
var projects = session.Load<Project>(projectsIds);

我是对的吗?我问,因为在博客中有关于单个实体(Customer)的演讲,而我想要检索整个最新的集合(IList<Project>)。

接下来,如果在获取与配置文件相关的新项目时,我想在索引配置文件中同时搜索(因为我没有他们的ID),例如下面(不允许这样的构造):

var profile = session.Include<Project>(x => x.Id)
                     .Query<Profile>()
                     .Single(x => x.User == "jwa");   

可以通过某种方式完成,还是需要我“自己”检索最新的项目,如下所示?

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);

它应该重新设计为更关系吗?

1 个答案:

答案 0 :(得分:1)

我很抱歉,但我很难回答你的问题,但是,我会尽力为你的情况提供更一般的答案。

包含功能的目的是消除对数据库的后续请求的需要,以便获取在初始请求中获得id的文档。假设您有这样的模型:

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; }
}

现在,如果要加载配置文件,可以执行以下操作:

var profile = session.Load<Profile>("profiles/daniel");

这将为您提供配置文件,其中包含此用户所有喜爱项目的ID。假设您要显示项目名称列表。你会做这样的事情:

var favoriteProjects = session.Load<Project>(profile.FavoriteProjectIds);

这很好用,但它会向数据库发出两个请求。如果您在嵌入模式下运行,这对您来说无关紧要,但如果您有远程ravendb服务器,您可能想要消除第二个http请求,因为您无论如何都不需要它。 .Include<T>()功能派上用场:

var profile = session.Include<Profile>(x => x.FavoriteProjectIds)
                     .Load("profiles/daniel");
var favoriteProjects = session.Load<Project>(profile.FavoriteProjectIds);

此代码只会进入数据库一次,这就是它更快的原因。注意我们如何在Include调用中指定类型参数 - 这只是因为我们希望在参数中支持lambda表达式而与所包含文档的类型无关。

这是使用Include的工作原理。为了简单起见,我建议采用这种方法。如果你真的想要获得最大的性能,或者你有其他理由不采用这种方法(例如,配置文件和项目不在同一个分片上的分片环境),你可以对某些进行非规范化部分项目进入您的个人资料。

这里需要注意的重要事项 - 您永远不希望在配置文件中对整个项目文档进行非规范化。假设您需要在应用程序的某个位置显示用户的个人资料页面。在此页面上,您还需要显示用户最喜欢的项目列表。你真的需要显示那个列表吗?

在这种情况下,您只需要对项目名称进行非规范化,以构建一个正确的html链接(无论如何都有id)。在代码中,您可能会有一个如下所示的类:

public class DenormalizedProject
{
    public string Id { get; set; }
    public string Name { get; set; }
}

...您的个人资料类将包含这样的属性......

public IList<DenormalizedProject> FavoriteProjects { get; set; }

使用这种方法,您可以拥有在一个文档中显示配置文件页面所需的一切,甚至不需要使用Include来获取该信息。 但是,这需要付出代价 - 您现在必须维护非规范化项目名称。如果它是不可变的,那很好,但在大多数情况下,用户可以更改它,然后你必须确保所有非规范化引用也得到更新(使用批处理命令或只加载和更新所有包含的文档)。在执行非规范化方法之前,您肯定要考虑这一点。