用于音乐数据库的 RavenDB
RavenDB for Music Database
RavenDB 是否适合构建音乐数据库,我需要您的建议。
我想在 C# Windows 应用程序中使用嵌入式版本。
目前数据库基于 SQL 并进行了规范化,例如有表Artist、Album、Genre、Share(音乐的主文件夹 collection)、Folder、Song,然后是一堆表格,用于建立 AlbumArtist、GenreSong、ArtistSong、ComposerSong、ConductorSOng 等关系。我想你会明白了。
现在使用 RavenDB,我可以将每首歌曲存储为包含所有信息的文档,但随后我会为每首歌曲乘以 ArtistNAme、AlbumName 甚至文件夹。
发现我可以将艺术家、流派等分开并在我的查询中使用 Includes,但是我如何 运行 然后查询为我提供流派为 "Rock" 的所有歌曲或特定艺术家的所有专辑?
我的理解是我需要一个索引才能将包含的文档中的属性用作查询的一部分。否则我会得到编译错误。正确的?
所以基本上我需要构建一个包含用户可能查询的所有字段的大型索引。
或者还有其他我没看到的方法吗?
虽然您可以 "include" 索引中其他文档的属性(使用 LoadDocument),但不建议广泛使用,因为需要更频繁地重建索引。
在您的情况下,您可以对 Song 文档进行建模,以通过 ID 和查询来包含对艺术家、流派等的引用,然后使用 Transformer 将结果转换为所需的结果 "view model"。在转换器中使用 LoadDocument 来获取艺术家姓名、流派名称等和 return 转换后的结果。转换是根据请求在服务器端执行的。
您的歌曲实体(简化版)可能如下所示:
public class Song
{
public string Id { get; set; }
public string Name { get; set; }
public string ArtistId { get; set; }
}
像这样的索引:
public class Song_ByArtist : AbstractIndexCreationTask<Song>
{
public Song_ByArtist()
{
Map = songs => from song in songs
select new
{
song.Name,
song.ArtistId
};
}
}
结合变压器:
public class Song_Artist_Transformer : AbstractTransformerCreationTask<Song>
{
public Song_Artist_Transformer()
{
TransformResults = results => from song in results
let artist = LoadDocument<Artist>(song.ArtistId)
select new SongArtistViewModel
{
SongName = song.Name,
ArtistName = artist.Name
};
}
}
您可以按艺术家查询歌曲和 return 包含艺术家姓名的视图模型:
using (var session = _documentStore.OpenSession())
{
var results = session.Query<Song, Song_ByArtist>()
.Where(x => x.ArtistId == "artists/1")
.TransformWith<Song_Artist_Transformer, SongArtistViewModel>();
}
这将 return 艺术家 "artists/1" 的所有歌曲转换为具有歌曲名称和艺术家名称的视图模型。
所以底线是:为您的歌曲文档建模以在需要时包含对其他文档的引用(如果遵循 DDD,则聚合),然后包含使用转换器所需的信息。 Transformers 可以看作是关系数据库中的 "View"。
注意:为您的歌曲文档创建一个组合索引,在其中索引所有属性(包括歌曲属性和引用),然后根据需要使用多个转换器来呈现数据。通常最好为每个文档使用一个 "large" 索引,而不是为同一文档类型使用几个小索引。在这个例子中,为了简单起见,我只映射了名称和艺术家 ID。
希望对您有所帮助!
数据便宜。
我建议复制数据,只要它相对简单,如艺术家名称、专辑名称和文件夹名称。特别是如果您认为它们不会改变。但是,如果它们发生变化,您当然必须在每首歌曲上更新它们。
如果您开始为艺术家姓名等简单的内容添加 include,那么您将在不必要时添加大量可笑的复杂内容。
对于 artists/albums/genre/etc,您可以构建 map-reduce 索引,按艺术家或流派或您感兴趣的任何内容对歌曲进行分组。map-reduce 的结果可以是任何您想要的,只是一个列表歌曲 ID 或者您可以包含所有歌曲数据的列表。然后根据您分组的内容查询索引。
因为 artist/album/genre 与歌曲紧密相关 - 让您的歌曲定义库中的艺术家和专辑,而不是为它们创建单独的文档,您可能会从中受益。这使得 add/edit/delete 歌曲更容易 - 如果你添加一首新艺术家的歌曲 - 突然你有一个新艺术家!如果您删除给定专辑的所有歌曲 - 专辑突然消失了!
如果你想实现播放列表之类的东西(应该有自己的文档)——播放列表文档可以只有一个歌曲 ID 列表,当你加载播放列表时,你可以轻松地包含所有歌曲.
对于更复杂的场景 - 如果您想显示用户播放列表的列表以及有关所包含歌曲的一些总体数据(例如,该播放列表中有哪些歌曲类型?),您可以构建一个索引来加载每个播放列表的所有相关歌曲,并从歌曲中吐出一个流派列表。然后查询索引即可。
可以在 this 博客 post 中找到有关文档存储与关系数据库的好读物。此外,它展示了一些如何在文档存储中存储电影数据库(我觉得它在文档关系方面与音乐存储非常相似)。
在 RavenDB 中,您可以创建 Map/Reduce 索引,这些索引可用于帮助您合并来自不同文档的信息,而且它通常比在索引时间加载文档(即使用 LoadDocument)更便宜(如@Jaynard 所述) ).
public class Song
{
public string Id { get; set; }
public string Name { get; set; }
public string ArtistId { get; set; }
}
public class Artist
{
public string Id {get;set;}
public string Name {get;set;}
}
public class SongsByArtist : AbstractMultiMapIndexCreationTask<SongsByArtist.ArtistSongs>
{
public class ArtistSongs
{
public string Id { get; set; }
public string Name { get; set; }
public IEnumerable<object> Songs { get; set; }
}
public SongsByArtist()
{
AddMap<Artist>(artists => from artist in artists
select new ArtistSongs
{
Id = artist.Id,
Name = artist.Name,
Songs = new List<object>()
});
AddMap<Song>(songs => from song in songs
select new ArtistSongs
{
Id = song.ArtistId,
Name = null,
Songs = new List<object> { new { song.Id, song.Name } }
});
Reduce = results => from result in results
group result by result.Id
into g
select new ArtistSongs
{
Id = g.Key,
Name = g.First(x => x.Name != null).Name,
Songs = g.SelectMany(x => x.Songs)
};
}
}
以及证明这一点的测试:
public class CanGetArtistSongs : RavenTestBase
{
[Fact]
public void WillSupportLast()
{
using (var store = NewDocumentStore())
{
using (var session = store.OpenSession())
{
session.Store(new Artist { Id = "artists/1", Name = "Pink Floyd" });
session.Store(new Song { Name = "Shine On You Crazy Diamond Part I", ArtistId = "artists/1"});
session.Store(new Artist { Id = "artists/2", Name = "Metallica" });
session.Store(new Song { Name = "Whiplash", ArtistId = "artists/2"});
session.Store(new Song { Name = "One", ArtistId = "artists/2"});
session.SaveChanges();
}
new SongsByArtist().Execute(store);
using (var session = store.OpenSession())
{
var results = session.Query<SongsByArtist.ArtistSongs, SongsByArtist>()
.Customize(customization => customization.WaitForNonStaleResults())
.Where(x => x.Name == "Metallica")
.ToList();
Assert.Empty(store.DatabaseCommands.GetStatistics().Errors);
Assert.Equal(2, results.First().Songs.Count());
}
}
}
}
RavenDB 是否适合构建音乐数据库,我需要您的建议。 我想在 C# Windows 应用程序中使用嵌入式版本。
目前数据库基于 SQL 并进行了规范化,例如有表Artist、Album、Genre、Share(音乐的主文件夹 collection)、Folder、Song,然后是一堆表格,用于建立 AlbumArtist、GenreSong、ArtistSong、ComposerSong、ConductorSOng 等关系。我想你会明白了。
现在使用 RavenDB,我可以将每首歌曲存储为包含所有信息的文档,但随后我会为每首歌曲乘以 ArtistNAme、AlbumName 甚至文件夹。
发现我可以将艺术家、流派等分开并在我的查询中使用 Includes,但是我如何 运行 然后查询为我提供流派为 "Rock" 的所有歌曲或特定艺术家的所有专辑?
我的理解是我需要一个索引才能将包含的文档中的属性用作查询的一部分。否则我会得到编译错误。正确的? 所以基本上我需要构建一个包含用户可能查询的所有字段的大型索引。
或者还有其他我没看到的方法吗?
虽然您可以 "include" 索引中其他文档的属性(使用 LoadDocument),但不建议广泛使用,因为需要更频繁地重建索引。
在您的情况下,您可以对 Song 文档进行建模,以通过 ID 和查询来包含对艺术家、流派等的引用,然后使用 Transformer 将结果转换为所需的结果 "view model"。在转换器中使用 LoadDocument 来获取艺术家姓名、流派名称等和 return 转换后的结果。转换是根据请求在服务器端执行的。
您的歌曲实体(简化版)可能如下所示:
public class Song
{
public string Id { get; set; }
public string Name { get; set; }
public string ArtistId { get; set; }
}
像这样的索引:
public class Song_ByArtist : AbstractIndexCreationTask<Song>
{
public Song_ByArtist()
{
Map = songs => from song in songs
select new
{
song.Name,
song.ArtistId
};
}
}
结合变压器:
public class Song_Artist_Transformer : AbstractTransformerCreationTask<Song>
{
public Song_Artist_Transformer()
{
TransformResults = results => from song in results
let artist = LoadDocument<Artist>(song.ArtistId)
select new SongArtistViewModel
{
SongName = song.Name,
ArtistName = artist.Name
};
}
}
您可以按艺术家查询歌曲和 return 包含艺术家姓名的视图模型:
using (var session = _documentStore.OpenSession())
{
var results = session.Query<Song, Song_ByArtist>()
.Where(x => x.ArtistId == "artists/1")
.TransformWith<Song_Artist_Transformer, SongArtistViewModel>();
}
这将 return 艺术家 "artists/1" 的所有歌曲转换为具有歌曲名称和艺术家名称的视图模型。
所以底线是:为您的歌曲文档建模以在需要时包含对其他文档的引用(如果遵循 DDD,则聚合),然后包含使用转换器所需的信息。 Transformers 可以看作是关系数据库中的 "View"。
注意:为您的歌曲文档创建一个组合索引,在其中索引所有属性(包括歌曲属性和引用),然后根据需要使用多个转换器来呈现数据。通常最好为每个文档使用一个 "large" 索引,而不是为同一文档类型使用几个小索引。在这个例子中,为了简单起见,我只映射了名称和艺术家 ID。
希望对您有所帮助!
数据便宜。
我建议复制数据,只要它相对简单,如艺术家名称、专辑名称和文件夹名称。特别是如果您认为它们不会改变。但是,如果它们发生变化,您当然必须在每首歌曲上更新它们。
如果您开始为艺术家姓名等简单的内容添加 include,那么您将在不必要时添加大量可笑的复杂内容。
对于 artists/albums/genre/etc,您可以构建 map-reduce 索引,按艺术家或流派或您感兴趣的任何内容对歌曲进行分组。map-reduce 的结果可以是任何您想要的,只是一个列表歌曲 ID 或者您可以包含所有歌曲数据的列表。然后根据您分组的内容查询索引。
因为 artist/album/genre 与歌曲紧密相关 - 让您的歌曲定义库中的艺术家和专辑,而不是为它们创建单独的文档,您可能会从中受益。这使得 add/edit/delete 歌曲更容易 - 如果你添加一首新艺术家的歌曲 - 突然你有一个新艺术家!如果您删除给定专辑的所有歌曲 - 专辑突然消失了!
如果你想实现播放列表之类的东西(应该有自己的文档)——播放列表文档可以只有一个歌曲 ID 列表,当你加载播放列表时,你可以轻松地包含所有歌曲.
对于更复杂的场景 - 如果您想显示用户播放列表的列表以及有关所包含歌曲的一些总体数据(例如,该播放列表中有哪些歌曲类型?),您可以构建一个索引来加载每个播放列表的所有相关歌曲,并从歌曲中吐出一个流派列表。然后查询索引即可。
可以在 this 博客 post 中找到有关文档存储与关系数据库的好读物。此外,它展示了一些如何在文档存储中存储电影数据库(我觉得它在文档关系方面与音乐存储非常相似)。
在 RavenDB 中,您可以创建 Map/Reduce 索引,这些索引可用于帮助您合并来自不同文档的信息,而且它通常比在索引时间加载文档(即使用 LoadDocument)更便宜(如@Jaynard 所述) ).
public class Song
{
public string Id { get; set; }
public string Name { get; set; }
public string ArtistId { get; set; }
}
public class Artist
{
public string Id {get;set;}
public string Name {get;set;}
}
public class SongsByArtist : AbstractMultiMapIndexCreationTask<SongsByArtist.ArtistSongs>
{
public class ArtistSongs
{
public string Id { get; set; }
public string Name { get; set; }
public IEnumerable<object> Songs { get; set; }
}
public SongsByArtist()
{
AddMap<Artist>(artists => from artist in artists
select new ArtistSongs
{
Id = artist.Id,
Name = artist.Name,
Songs = new List<object>()
});
AddMap<Song>(songs => from song in songs
select new ArtistSongs
{
Id = song.ArtistId,
Name = null,
Songs = new List<object> { new { song.Id, song.Name } }
});
Reduce = results => from result in results
group result by result.Id
into g
select new ArtistSongs
{
Id = g.Key,
Name = g.First(x => x.Name != null).Name,
Songs = g.SelectMany(x => x.Songs)
};
}
}
以及证明这一点的测试:
public class CanGetArtistSongs : RavenTestBase
{
[Fact]
public void WillSupportLast()
{
using (var store = NewDocumentStore())
{
using (var session = store.OpenSession())
{
session.Store(new Artist { Id = "artists/1", Name = "Pink Floyd" });
session.Store(new Song { Name = "Shine On You Crazy Diamond Part I", ArtistId = "artists/1"});
session.Store(new Artist { Id = "artists/2", Name = "Metallica" });
session.Store(new Song { Name = "Whiplash", ArtistId = "artists/2"});
session.Store(new Song { Name = "One", ArtistId = "artists/2"});
session.SaveChanges();
}
new SongsByArtist().Execute(store);
using (var session = store.OpenSession())
{
var results = session.Query<SongsByArtist.ArtistSongs, SongsByArtist>()
.Customize(customization => customization.WaitForNonStaleResults())
.Where(x => x.Name == "Metallica")
.ToList();
Assert.Empty(store.DatabaseCommands.GetStatistics().Errors);
Assert.Equal(2, results.First().Songs.Count());
}
}
}
}