RavenDB Database for Music

I need your advice on whether RavenDB is suitable for creating a music database. I would like to use the embedded version in a Windows C # application.

Currently, the database is based on SQL with normalization in place, having tables, for example. Artist, album, genre, sharing (main folder of the music collection), folder, song, and then a bunch of tables to create relationships like AlbumArtist, GenreSong, ArtistSong, ComposerSong, ConductorSOng, etc. I think you will get it.

Now with RavenDB I could store every Song as a Document containing all the information, but then I would multiply ArtistNAme, AlbumName and even Folder for each song.

It turned out that I can separate Artist, Genre, etc. and use Include in my query, but how would I run a query that gives me all the songs with the "Rock" genre or all the albums for a particular artist?

I understand that I need an index in order to be able to use Properties from the included document as part of the request. Otherwise, I would get compilation errors. Right? So basically, I would have to create one large index containing all the fields that the user could execute.

Or is there another way that I do not see?

+1
source share
3 answers

Although you can "include" properties from other documents in the index (using LoadDocument), it is not recommended that you use it extensively because you need to rebuild the index more often.

In your case, you can simulate your song document to include links to Artist, Genre, etc. by id and request on this subject, and then use Transformer to convert the result to the desired “view model”. Use LoadDocument in the transformer to select artist name, genre name, etc. And return the converted result. Conversion is performed on the server side upon request.

Your song essence (simplified) might look like this:

public class Song { public string Id { get; set; } public string Name { get; set; } public string ArtistId { get; set; } } 

And an index like this:

 public class Song_ByArtist : AbstractIndexCreationTask<Song> { public Song_ByArtist() { Map = songs => from song in songs select new { song.Name, song.ArtistId }; } } 

In combination with a transformer:

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

You can request artists songs and return a performance model, including artist name, with:

 using (var session = _documentStore.OpenSession()) { var results = session.Query<Song, Song_ByArtist>() .Where(x => x.ArtistId == "artists/1") .TransformWith<Song_Artist_Transformer, SongArtistViewModel>(); } 

This will return all the songs for the artist / 1 artist, transformed as a presentation model with the song name and artist name.

So, on the bottom line: model your song document to include links to other documents (aggregates, if after DDD), where necessary, and then include the information you need using transformers. Transformers can be thought of as something like “View” in relational db.

Note. Make one combined index for your song’s document, where you index all the properties (both song properties and links), and then use several transformers to represent the data as needed. It is often better to use one large index for one document instead of several small ones for the same type of document. In this example, I just matched the name and identifier of the artist so that it is simple.

Hope this helps!

0
source

Data is cheap.

I would suggest duplicating the data as long as their relatively simple artist name, album name and folder name. Especially if you do not think that they will change. But if they change, you will certainly have to update them on every song.

If you start making inclusions for simple things, such as the artist’s name, then you will add ridiculous complexity when you don’t need it.

For artists / albums / genres / etc. you can create downsized indexes that group songs by artist or genre or whatever interests you. The result of the map reduction can be whatever you want, just a list of song identifiers, or you can include a list of all the song data. Then query the index by what you are grouping.

Because the artist / album / genre is so closely connected with the songs, it may come in handy that your songs determine which artists and albums are in the library, instead of having separate documents for them. This makes it easy to add / edit / delete songs - if you add a song with a new artist - suddenly you have a new artist! If you delete all the songs on this album - suddenly the album is gone!

If you want to implement something like playlists (which should have their own documents), there can only be a list of song identifiers in a playlist document, and when you load a playlist, you can easily turn on all songs.

For a more complex scenario - if you want to show a list of user playlists along with some general information about the included songs (for example, what genres of songs are in this playlist?), You can create an index that downloads all the related songs for each playlist and splashes out the list of genres from songs. Then just query the index.

0
source

A good read about document repositories and relational databases can be found in this blog post. Furterhmore, this shows a bit of how you can store the Movie database in a document repository (which, in my opinion, is very similar to the Music store in relation to document relationships).

In RavenDB, you can create Map / Reduce indexes that can be used to merge information from different documents, and it is usually cheaper (as indicated by @Jaynard) than loading documents at index time (i.e. using LoadDocument).

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

And a test to prove it:

 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()); } } } } 
0
source

All Articles