4462

RavenDB for Music Database

Question:

I need your advice whether RavenDB would be suitable for building a Music Database. I would like to use the embedded version in a C# Windows Application.

Currently the database is based on SQL with normalisation in place, having tables for e.g. Artist, Album, Genre, Share (main folder of music collection), Folder, Song and then a bunch of tables to build the relations like AlbumArtist, GenreSong, ArtistSong, ComposerSong, ConductorSOng, etc. I think you'll 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 the Folder for every song.

Figured out that I could separate Artist, Genre, etc. and use Includes in my Query, but how would i run then a query which gives me all Songs with a Genre of "Rock" or all Albums for a specific Artist?

My understanding is that i would need an Index to be able to use Properties from an included document as part of a query. Otherwise I would get compilation errors. Right? So basically I would need to build one large index containing all the fields that a user might do a query.

Or is there any other way, which I don't see?

Answer1:

While you can "include" properties from other documents in an index (using LoadDocument) it's not recommended to use extensively as the index need to be rebuilt more often.

In your case you could model your Song document to include references to Artist, Genre etc by id and query on that and then use a Transformer to transform the result to desired "view model". Use LoadDocument in the transformer to fetch artist name, genre name etc and return the transformed result. The transformation is performed server side on request.

Your song entity (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 }; } }

Combined 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 query for songs by artists and return a view model including the 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 would return all songs for artist "artists/1" transformed as a view model with song name and artist name.

So the bottom line is: model your song document to include references to other documents (aggregates if following DDD) where needed and then include the information needed by using transformers. Transformers could be looked at sort of like a "View" in a relational db.

Note: Make one combined index for your song document where you index all properties (both song properties and references) and then use multiple transformers to present the data as needed. It's often better to use one "large" index per document instead of several small for the same document type. In this example I only mapped the name and artist id to keep it simple.

Hope this helps!

Answer2:

Data is cheap.

I would suggest duplicating the data as long as its relatively simple like artist name, album name, and folder name. Especially if you don't think they will change. But if they change you'll have to update them on each song of course.

If you start doing includes for simple things like artist name, then you'll be adding a ridiculous amount of complexity when its not necessary.

For artists/albums/genre/etc you can build map-reduce indexes that group the songs by artist or genre or whatever you're interested in. The result of the map-reduce can be whatever you want, just a list of song ids or you can include a list of all the song data. Then query the index by whatever you're grouping on.

Because artist/album/genre are so tightly coupled to the songs - you might benefit from letting your songs define what artists and albums are in the library, instead of having separate documents for them. This makes it easier to add/edit/delete songs - if you add a song with a new artist - suddenly u have a new artist! If you delete all the songs of a given album - suddenly the album is gone!

If you want to implement something like playlists (which should have their own documents) - the playlist document could just have a list of song ids, and when you load the playlist you can easily do an include for all the songs.

For a more complex scenario - if you wanted to show a list of the users playlists along with some overall data about the songs included (e.g. what genres of songs are in this playlist?) you can build an index that loads all the associated songs for each playlist and spits out a list of genres from the songs. Then just query the index.

Answer3:

Good reading about document stores vs relational databases may be found in <a href="http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/" rel="nofollow">this</a> blog post. Furterhmore, it shows a bit of how one can store a Movie database in a document store (which I feel it is pretty similar to a Music store in terms of documents relationships).

In RavenDB you can create Map/Reduce indexes that can be used to help you merging information from different documents and it is usually cheaper (as stated by @Jaynard) than loading documents in 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 this:

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

Recommend

  • Decomposing equality of constructors with match expressions in Coq
  • Quartz Scheduler locks the database user
  • Django ModelForm with foreign key
  • Query Soundcloud API using created_at filter
  • Explode array of structs to columns in Spark
  • can't get the image to rotate in center in Qt
  • How Intersect and Except result in SSIS
  • UIView animation with CGAffineTransformMakeScale changes size instantly with decimal number
  • Getting “node stack overflow” when cbind multiple sparse matrices
  • How to tell Xalan to escape characters in HTML attributes
  • Number of input rows in spark structured streaming with custom sink
  • XslTransform with xml-stylesheet
  • Remove annotation while keeping plot matplotlib
  • Omitting XML Declaration when invoking Transformer with StAXResult
  • cross combine two RDDs using pyspark
  • Eclipse WTP update Error . What to do Next?
  • xamarin ios 10 open app store application programmatically in my app
  • Changing One Tag Name in an XML File Using XSLT
  • Adding Extra Data to Auth Cookie after login - MVC 5
  • Why async.map function works with the native fs.stat function?
  • is uninitialized_copy/fill(In first, In last, For dest, A &a) an oversight in the c++ standard?
  • Is there any point in using DI for class injections
  • find xml element by attribute
  • spring arbitrary messaging tcp socket
  • @Router in Spring Integration with annotations (request/reply)
  • MySQL select genres issue (php)
  • SOLR - Querying Facets, return N results per Facet
  • How are 32 bit JavaScript numbers resulting from a bit-wise operation converted back to 64 bit numbe
  • Insert audio album in Android MediaStore
  • Ruby on Rails App deployed to heroku showing “We're sorry, but something went wrong”
  • Julia: How to give multiple workers access to functions that are 'include(…)' into a modul
  • In matplotlib, how do you change the fontsize of a single figure?
  • JPA flush vs commit
  • Row Count Is Returning the incorrect number using RaptureXML
  • htaccess rewriting URLs with multiple forward slashes
  • Warning: Can't call setState (or forceUpdate) on an unmounted component
  • Why joiner is not used after Sequence generator or Update statergy
  • Is there any way to bind data to data.frame by some index?
  • Django query for large number of relationships
  • Why is Django giving me: 'first_name' is an invalid keyword argument for this function?
  • How can I use `wmic` in a Windows PE script?
  • How to push additional view controllers onto NavigationController but keep the TabBar?