0

我在 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 博客上读过可以使用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);

是否应该重新设计以更具关联性?

4

1 回答 1

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 来获取该信息。但是,这是有代价的——您现在必须维护非规范化的项目名称。如果它是不可变的,很好,但在大多数情况下,用户可以更改它,然后您必须确保所有非规范化引用也得到更新(使用批处理命令或只是加载和更新所有包含的文档)。在采用非规范化方法之前,您肯定要考虑这一点。

于 2012-05-13T21:23:15.133 回答