1 / 44

Code-First .NET and MongoDB

The Ubiquitous Blog Example Philly ALT.NET User Group 2010-11-16 John C. Zablocki Development Lead, MagazineRadar Adjunct, Fairfield University. Code-First .NET and MongoDB. Agenda. MongoDB Basic Concepts MongoDB Shell NoRM MongoDB C# Driver MongoDB Design Considerations

Mercy
Download Presentation

Code-First .NET and MongoDB

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. The Ubiquitous Blog Example Philly ALT.NET User Group 2010-11-16 John C. Zablocki Development Lead, MagazineRadar Adjunct, Fairfield University Code-First .NET and MongoDB

  2. Agenda • MongoDB Basic Concepts • MongoDB Shell • NoRM • MongoDB C# Driver • MongoDB Design Considerations • In Depth: Meringue • Case Study: RateMySnippet • Questions?

  3. MongoDB Concepts • Schema-less documents stored in collections • Documents are stored as BSON (Binary JSON) • JavaScript used to query and manipulate documents and collections • Each document in a collection has a unique BSON ObjectId field named _id • Collections belong to a database

  4. Installing MongoDB on Windows • Download the binaries from mongodb.org • Extract to Program Files directory (or wherever) • Create a directory c:\data\db • Run mongod.exe from the command line with the --install switch • See http://bit.ly/aed1RW for some gotchas • To run the daemon without installing, simply run mongod.exe without arguments • Run mongo.exe to verify the daemon is running

  5. A funny thing happened on the way to Philadelphia... MongoDB and .NET

  6. MongoDB - Shell • The MongoDB interactive JavaScript shell (mongo.exe) is a command line utility for working with MongoDB servers • Allows for CRUD operations on collections • May be used for basic administration • Creating indexes • Cloning databases • Also useful as a test-bed while building apps

  7. MongoDB - Shell /*Connect to a server:port/database (defaults are localhost:27017/test ):*/ mongo.exelocalhost:27017/AltNetGroup //Switch database: use CodeCamp //View collections in a database: showcollections //create an index on Name field db.Posts.ensureIndex({ Name : 1 }); //copy one database to another db.copyDatabase("CodeCamp", "AltNetGroup")

  8. //create a document varpost = { Title: "On Installing MongoDB as a Service on Windows" } //insert a document, if the collection doesn't exist it's created db.Posts.insert(post); //verify that the document was created db.Posts.find(); //write a query to find a post with a valid title varquery = { Title: { $ne: null} } //use that query to find the post varpost = db.Posts.findOne(query); //this line will actually set the content after pressing enter post.Content = "When installing MongoDB as a service on Windows..." MongoDB - Shell

  9. //update the content to include an author using collection update method db.Posts.update( { Title : "On Installing MongoDB as a Service on Windows" }, { Author : "John Zablocki" } ) //check that the post was updated db.Posts.findOne() //where'd my document go? updates are in place, replacing entire documents! //need to use the $set operator to update partial documents - let's start over //first remove the new document. Notice how remove takes a func as an argument. //find and findOne also accept functions as arguments db.Posts.remove(function (e) { returnthis.Author == "John Zablocki" }) //rerun the first statements up to but not including the db.Posts.update(... db.Posts.update({ Title: "On Installing MongoDB as a Service on Windows" }, { $set: { Author: "John Zablocki" } }) //verify that the update worked db.Posts.findOne() MongoDB - Shell

  10. //add a tag to the post db.Posts.update({ Title: "On Installing MongoDB as a Service on Windows" }, { $push: { Tags: "mongodb" } }) //look for that tag db.Posts.findOne() //add two more tags db.Posts.update({ Title: "On Installing MongoDB as a Service on Windows" }, { $pushAll: { Tags: ["windows", "nosql"] } }) //add another post db.Posts.insert( { Author : "John Zablocki", Title : "On MapReduce in MongoDB", Tags: ["mongodb", "nosql"] }) //verify that last insert worked db.Posts.findOne(function (e) { returnthis.Title.indexOf("MapReduce") != -1; }) MongoDB - Shell

  11. //add a "like" counter to the post. The booleans arguments tell update //not to insert if the document doesn't exist and to update all documents, //not just one respectively db.Posts.update({ Author: "John Zablocki" }, { $set: { Likes: 0} }, false, true) //increment the likes counter for the mapreduce article db.Posts.update({ Title: /mapreduce/i }, { $inc: { Likes: 1} }) //check that the counter was incremented db.Posts.findOne({ Title: /mapreduce/i }).Likes MongoDB - Shell

  12. MongoDB – Shell /* Create the map function This function creates a view that looks like{ “mvc”, [1, 1] }, { “norm”, [1] }*/ varmap = function() { if (!this.Tags) { return; } for (varindexinthis.Tags) { emit(this.Tags[index], 1); } };

  13. MongoDB – Shell MapReduce /* Create the reduce function conceptually, reduce gets called like: reduce("mvc", [1, 1]); reduce("norm", [1] /* varreduce = function(key, vals) { varcount = 0; for (varindexinvals) { count += vals[index]; } returncount; };

  14. MongoDB – Shell MapReduce /* Run the mapreduce command on the Posts collection using the map and reduce functions defined above store the results in a collection named Tags */ varresult = db.runCommand( { mapreduce : "Posts", map : map, reduce : reduce, out : "Tags" }); db.Tags.find()

  15. Something Completely Different

  16. NoRM

  17. NoRM • Developed by Andrew Theken among others • Active community with reliable support • I received help even as I prepared this slide! • Support for typed and untyped collections, MapReduce, Property Mapping, LINQ, Expando, nearly all CRUD operations • Will eventually sit on top of the 10gen officially supported driver

  18. NoRM – The Basics //Connections managed with IDisposable pattern //Connect to test database on localhost using (IMongomongo=Mongo.Create("mongodb://localhost/test")) { Console.WriteLine(mongo.Database.DatabaseName); } //Mongo instance has MongoDatabase property /*MongoDatabase has GetCollection<T> methods for accessing MongoCollection instances*/ //CRUD operations performed on collections

  19. NoRM - CRUD //Class properties map to document structure publicclassArtist { //ObjectId property mapped to _id field in document publicObjectIdId { get; set; } publicstringName { get; set; } //IList property will be mapped to JavaScript Array privateIList<string>_albums=newList<string>(0); publicIList<string>Albums { get { return_albums ; } set { _albums =value; } } }

  20. NoRM - CRUD varartist=newArtist() { Name="The Decembrists" }; //Inserting a document into a typed collectionmongo.Database.GetCollection<Artist>("Artists").Insert(artist); //Updating (replacing) a document in a typed collection artist.Name="The Decemberists"; mongo.Database.GetCollection<Artist>("Artists").Save(artist); //Updating a nested collection mongo.Database.GetCollection<Artist>("Artists").UpdateOne( new { Name="The Decemberists"}, new { Albums=M.Push("Picaresque") } );

  21. NoRM - CRUD //Find all documents in a typed collection varartists=mongo.GetCollection<Artist>("Artists").Find(); Console.WriteLine(artists.FirstOrDefault().Name); //Query with a document spec varartist=mongo.GetCollection<Artist>("Artists") .FindOne( new { Name="The Decemberists" }); Console.WriteLine(artist.Albums.Count); //Count the documents in a collection longcount=mongo.GetCollection<Artist>("Artists").Count(); Console.WriteLine(count);

  22. NoRM - MapReduce //Add a Tags collection added to Artist class privateIList<string>_tags; publicIList<string>Tags { get { return_tags; } set { _tags=value; } } //Add some tags – use Set not PushAll, since Tags was nullmongo.Database.GetCollection<Artist>("Artists").UpdateOne( new { Name="The Decemberists" }, new { Tags=M.Set(newList<string>() { "Alternative", "Folk Rock }) });

  23. NoRM - MapReduce //Create map and reduce functons stringmap=@"function() { if (! this.Tags ) { return; } for (index in this.Tags) { emit(this.Tags[index], 1); } }"; stringreduce=@"function(previous, current) { var count = 0; for (index in current) { count += current[index]; } return count; }";

  24. NoRM - MapReduce //MapReduce class is responsible for calling mapreduce command MapReducemr=mongo.Database.CreateMapReduce(); //Represents the document passed to the //db.runCommand in the shell example MapReduceOptionsoptions=newMapReduceOptions("Artists") { Map=map, Reduce=reduce, OutputCollectionName="Tags" }; MapReduceResponseresponse=mr.Execute(options); varcollection=mongo.Database.GetCollection<Tag>("Tags"); Console.WriteLine(collection.Count());

  25. NoRM - LINQ //LINQ provider exposed via AsQueryable method of MongoCollection varartists=mongo.Database.GetCollection<Artist>("Artists") .AsQueryable(); //Find items in typed collection varartistsWithDecInName=fromainmongo.Database.GetCollection<Artist>().AsQueryable() wherea.Name.Contains("Dec") selecta; Console.WriteLine("First containing Dec in name: "+artistsWithDecInName.First().Name);

  26. NoRM - LINQ //Find artists without pulling back nested collections //Note use of Regex name search varartistsWithoutAlbums= fromainmongo.Database.GetCollection<Artist>().AsQueryable() whereRegex.IsMatch(a.Name, "ber", RegexOptions.IgnoreCase) selectnew { Name=a.Name }; Console.WriteLine(artistsWithoutAlbums.First().Name); //Find artists with a given tag varartistsWithFolkRockTag=mongo.Database.GetCollection<Artist>("Artists") .AsQueryable().Where(a=>a.Tags.Any(s=>s=="Folk Rock")); Console.WriteLine(artistsWithFolkRockTag.First().Name);

  27. MongoDB C# Driver • 10gen developed and supported • Consists of two primary components, a BSON serializer and the MongoDB driver • Support for typed and untyped collections, MapReduce, and all CRUD operations • Currently lacking a LINQ provider • Released about two weeks ago as version 0.7!

  28. MongoDB C# Driver – The Basics • privatestaticMongoDatabase_mongoDatabase=null; • staticProgram() { • //MongoConnectioSettings used to create connection strings • MongoConnectionSettingssettings=newMongoConnectionSettings(); • settings.Address=newMongoServerAddress("localhost", 27017); • //MongoServer manages access to MongoDatabase • MongoServermongoServer=newMongoServer(settings); • //MongoDatabase used to access MongoCollection instances • _mongoDatabase=mongoServer.GetDatabase("AltNet"); • }

  29. MongoDB C# Driver - CRUD • varartist=newArtist() { Name="The Decembrists" }; • //Inserting a document into a typed collection_mongoDatabase.GetCollection<Artist>(COLLECTION).Insert(artist); • //Updating (replacing) a document in a typed collectionartist.Name="The Decemberists"; • _mongoDatabase.GetCollection<Artist>(COLLECTION).Save(artist); • //Updating a nested collection_mongoDatabase.GetCollection<Artist>(COLLECTION).Update( • Query.EQ("Name", "The Decemberists"),newBsonDocument("$pushAll",newBsonDocument("Albums", newBsonArray() { "Castaways and Cutouts", "Picaresque", "Hazards of Love", "The Crane Wife" })) • );

  30. MongoDB C# Driver - CRUD • //Find all documents in a typed collection • varartists=_mongoDatabase.GetCollection<Artist>(COLLECTION).FindAll(); • Console.WriteLine("Artist name: "+artists.FirstOrDefault().Name); • //Query with a document specvarartist=_mongoDatabase.GetCollection<Artist>(COLLECTION).FindOne(newBsonDocument { { "Name", "The Decemberists" } }); • Console.WriteLine("Album count: "+artist.Albums.Count); • //Count the documents in a collectionlongcount=_mongoDatabase.GetCollection<Artist>(COLLECTION).Count();Console.WriteLine("Document count: "+count);

  31. MongoDB C# Driver - CRUD • varartists=_mongoDatabase.GetCollection<Artist>(COLLECTION); • //Find items in typed collection • varartistsStartingWithThe=artists.Find(Query.Matches("Name", newRegex("the", RegexOptions.IgnoreCase))); • Console.WriteLine(artistsStartingWithThe.First().Name); • //Find artists without pulling back nested collectionsvarartistsWithDecInTheName=artists.Find(Query.Matches("Name", "Dec")).SetFields("Name"); • Console.WriteLine(artistsWithDecInTheName.First().Name); • ////Find artists with a given tag • varartistsWithIndieTag=artists.Find(Query.In("Tags", "Indie")); • Console.WriteLine( artistsWithIndieTag.First().Name);

  32. MongoDB C# Driver - MapReduce • //Add some tags • _mongoDatabase.GetCollection<Artist>(COLLECTION).Update(Query.EQ("Name", "The Decemberists"),newBsonDocument("$pushAll",newBsonDocument("Tags", newBsonArray() { "Folk rock", "Indie" })) ); • varartist=newArtist() { Name="Sunny Day Real Estate",Albums=newList<string>() { "How it Feels to be Something On", "Diary" },Tags=newList<string>() { "Indie", "Emo" }}; • _mongoDatabase.GetCollection<Artist>(COLLECTION).Save(artist);

  33. MongoDB C# Driver - MapReduce • _mongoDatabase.GetCollection<Artist>(COLLECTION).Save(artist); • //Create map and reduce functonsBsonJavaScriptmap=@"function() {...}"; • BsonJavaScriptreduce=@"function(previous, current) { ... }"; • varresult=_mongoDatabase.GetCollection<Artist>(COLLECTION).MapReduce(map, reduce, MapReduceOptions.SetKeepTemp(true).SetOutput("Tags"));

  34. In Depth: Meringue

  35. Design Considerations • Your object graph is your data model • Don't be afraid to store data redundantly • Your graph might be redundant • Not everything has to fit in 1 document • Don't be afraid to store aggregate statistics with a document.

  36. Object Graph as Data Model • Generally speaking, most MongoDB drivers will serialize an object graph as a single document • The relationships of your classes creates an implied schema! • Migrating this schema is not trivial if you are trying to deserialize properties that did not or no longer exist • Consider use cases carefully to avoid inefficiently structured documents • Projection queries will be your friend

  37. Redundant Data is OK • Optimize documents for quick reads and writes • Your application layer will have to maintain referential integrity! • If every time you access a Post document, you need some of an Author document's data, store that data with Post • Design simple classes for this redundant data for reusability (see AuthorInfo in Meringue)

  38. Do Not Stuff it In 1 Document • Nothaving formal relationships does not mean throwing away relationships • Consider a user and his or her logged actions • The user would likely have a User class/doc with properties for name, email, etc. • User actions are generally write heavy and read out of band. • Don't clutter user documents - create a separate collection for user actions

  39. Don't Be Afraid of Extra Data • The schema-less nature of documents makes it easy to store meta data about that document – particularly aggregate data • Consider a blog post with a rating feature • Each rating would be stored as a nested document of the post • Rather than compute vote totals and averages real time, simply add these properties to the document and update on writes

  40. Case Study: RateMySnippet.com

  41. Final Thoughts Eat food. Not too much. Mostly plants. - Michael Pollan

  42. Final Thoughts Write Code. Not too much. Mostly C#. - John Zablocki

  43. The Meringue Stack • ASP.NET MVC • MVCContrib • MongoDB • Mongo C# Driver • Spring.NET • Recoil • AutoMapper

  44. Links • http://dllHell.net - my blog • http://www.CodeVoyeur.com - my code • http://www.linkedin.com/in/johnzablocki • http://twitter.com/codevoyeur • http://mongodb.org - Official MongoDB site • http://bitbucket.org/johnzablocki/meringue • http://bitbucket.org/johnzablocki/codevoyeur-samples

More Related