我是否需要你的建议RavenDB是否适合构建音乐数据库。 我想在C#Windows应用程序中使用嵌入式版本。
目前,数据库基于具有规范化的SQL,具有例如表格。艺术家,专辑,流派,分享(音乐收藏的主要文件夹),文件夹,歌曲,然后是一堆表来建立像AlbumArtist,GenreSong,ArtistSong,ComposerSong,ConductorSOng等关系。我想你会得到它。< / p>
现在使用RavenDB,我可以将每首歌曲存储为包含所有信息的文档,但是我会为每首歌曲增加ArtistNAme,AlbumName甚至文件夹。
想出我可以将Artist,Genre等分开,并在我的查询中使用包含,但是我如何运行一个查询,它给了我所有歌曲的类型为“Rock”或特定艺术家的所有专辑?
我的理解是,我需要一个索引才能使用包含文档中的属性作为查询的一部分。否则我会得到编译错误。对? 所以基本上我需要构建一个包含用户可能进行查询的所有字段的大索引。
或者还有其他方法,我没有看到?
答案 0 :(得分:0)
虽然您可以在索引中“包含”其他文档中的属性(使用LoadDocument),但建议不要广泛使用,因为索引需要更频繁地重建。
在您的情况下,您可以对您的歌曲文档进行建模,以包含对艺术家,流派等的引用,并通过id和查询,然后使用Transformer将结果转换为所需的“视图模型”。在变换器中使用LoadDocument来获取艺术家名称,流派名称等,并返回转换后的结果。根据请求在服务器端执行转换。
您的歌曲实体(简化版)可能如下所示:
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
};
}
}
您可以按艺术家查询歌曲,并返回包含艺术家姓名的视图模型:
using (var session = _documentStore.OpenSession())
{
var results = session.Query<Song, Song_ByArtist>()
.Where(x => x.ArtistId == "artists/1")
.TransformWith<Song_Artist_Transformer, SongArtistViewModel>();
}
这将返回艺术家“artists / 1”的所有歌曲,并将其转换为带有歌曲名称和艺术家名称的视图模型。
所以最重要的是:根据需要为您的歌曲文档建模以包含对其他文档的引用(如果遵循DDD,则包含聚合),然后包含使用变换器所需的信息。变形金刚可能看起来有点像关系数据库中的“视图”。
注意:为您的歌曲文档制作一个组合索引,您可以在其中索引所有属性(歌曲属性和参考),然后使用多个变换器根据需要显示数据。对于相同的文档类型,每个文档使用一个“大”索引而不是几个小索引通常会更好。在这个例子中,我只映射了名称和艺术家ID,以保持简单。
希望这有帮助!
答案 1 :(得分:0)
数据便宜。
我建议复制数据,只要它相对简单,如艺术家姓名,专辑名称和文件夹名称。特别是如果你不认为他们会改变。但是,如果他们改变了你,那么当然每个歌曲都必须更新它们。
如果你开始为艺术家名称等简单的东西做包含,那么当你没有必要时,你会增加一些荒谬的复杂性。
对于艺术家/专辑/流派/等,您可以构建地图缩小索引,按照艺术家或流派或任何您感兴趣的内容对歌曲进行分组。地图缩减的结果可以是您想要的任何内容,只需歌曲ID列表,或者您可以包含所有歌曲数据的列表。然后按照您要分组的内容查询索引。
因为艺术家/专辑/流派与歌曲紧密相连 - 您可能会受益于让您的歌曲定义图书馆中的艺术家和专辑,而不是为他们提供单独的文档。这样可以更轻松地添加/编辑/删除歌曲 - 如果您使用新艺术家添加歌曲 - 突然间您会有一位新艺术家!如果删除给定专辑的所有歌曲 - 突然相册就不见了!
如果你想实现播放列表(应该有自己的文档)之类的内容 - 播放列表文档可能只有一个歌曲ID列表,当你加载播放列表时,你可以轻松地为所有歌曲做一个包含。 / p>
对于更复杂的场景 - 如果您想要显示用户播放列表的列表以及有关所包含歌曲的一些整体数据(例如,此播放列表中有哪些类型的歌曲?),您可以构建一个加载所有歌曲的索引每个播放列表的相关歌曲,并从歌曲中吐出一系列流派。然后只查询索引。
答案 2 :(得分:0)
关于文档存储与关系数据库的良好阅读可以在this博客文章中找到。另外,它显示了一个如何在文档存储中存储Movie数据库的方式(我觉得它在文档关系方面非常类似于音乐商店)。
在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());
}
}
}
}