Skip to content

Code Example: Verses

pmarkert edited this page May 17, 2012 · 1 revision

For me, sometimes the quickest way to get familiar with something is to see it in action. Additionally, I'm always looking for a chance to share the Gospel. :) Here is a compact, but complete sample project that demonstrates using Mongol to save and query some records. It showcases a few features, and provides enough of an example to understand basic operations. Create a new console application, add Mongol using Nuget, and add a Mongol.Url config setting. Then paste these classes in and hit run. The project downloads one of the easiest to parse copies of the bible I could find and loads the verses into Mongo, using them to demonstrate some CRUD and query operations.

// Class to represent a single bible-verse
public class Verse { 
	[BsonId]
	public string Id { get; set; }
	public string Book { get; set; }
	public int Chapter { get; set; }
	public int Number { get; set; }
	public string Text { get; set; }
}

// Class to demonstrate storing an object with an array property
public class Person { 
	[BsonId]
	public string Id { get; set; }
	public string[] FavoriteVerses { get; set; }
}

public class VerseManager : RecordManager<Verse> {
	protected override void Initialize() { 
		// Unique index on Book, Chapter, Verse
		EnsureIndex(IndexKeys
			.Ascending(PropertyName(x => x.Book))
			.Ascending(PropertyName(x => x.Chapter))
			.Ascending(PropertyName(x => x.Number))
		, IndexOptions.SetUnique(true));
	}

	public IEnumerable<Verse> GetByBook(string book) { 
		// Example of a simple single-field query
		return Find(Query.EQ(PropertyName(x => x.Book), book));
	}

	public Verse GetByReference(string book, int chapter, int verse) { 
		// Exmaple of a multi-field query
		return FindSingle(
			Query.EQ(PropertyName(x => x.Book), book)
			.And(Query.EQ(PropertyName(x => x.Chapter), chapter))
			.And(Query.EQ(PropertyName(x => x.Number), verse)));
	}

	public IEnumerable<Verse> GetByBookAndVerseRange(string book, int lowChapter, int lowVerse, int highChapter, int highVerse) {
		// Example of a range query joined with multiple conditions
		return Find(Query.EQ(PropertyName(x => x.Book), book) 
			.And((Query.GTE(PropertyName(x => x.Chapter), lowChapter)
			.And(Query.GTE(PropertyName(x => x.Number), lowVerse)))
			.And((Query.LTE(PropertyName(x => x.Chapter), highChapter)
			.And(Query.LTE(PropertyName(x => x.Number), highVerse))))
		));
	}

	public void ReloadAll(IEnumerable<Verse> verses) { 
		// Reloads all records in the collection, so you can run the program more than once
		collection.RemoveAll();
		collection.InsertBatch(verses);
	}

		
	// Returns the number of verses in each book, sorted by descending count of verses.
	public Dictionary<string, int> CalculateBookStats() { 
		// Demonstrates the new Aggregation Framework and Mongol's DSL wrapper
		return Aggregate(
				Aggregation.Group(
					Aggregation.Grouping.By(PropertyName(x => x.Book)), 
					Aggregation.Grouping.Count("Count")),
				Aggregation.Sort(Aggregation.Sorting.By("Count", false))
		).ToDictionary(x => x[ID_FIELD].AsString, x => x["Count"].AsInt32);
	}
}

class Program {
	static void Main(string[] args) {
		var verseManager = new VerseManager();
		
		// I prefer more modern translations than KJV, but this was freely available and easy to parse with a few lines.
		verseManager.ReloadAll(parseVerses(new StreamReader(new GZipStream(new WebClient().OpenRead("https://s3.amazonaws.com/mongol.ephisys.com/Data/kjv.rawtxt.gz"), CompressionMode.Decompress))));

		// Print some verse-count statistics by book using aggregation framework and Mongol's DSL wrapper (requires Mongo version>=2.1, otherwise comment these out)
		foreach (var entry in verseManager.CalculateBookStats()) {
			Console.WriteLine(entry.Key + " - " + entry.Value);
		}

		// Print verses containing the word love using a linq query
		var re = new Regex(@"\blove\b", RegexOptions.IgnoreCase);
		foreach (var verse in verseManager.AsQueryable.Where(x => re.IsMatch(x.Text))) {
			Console.WriteLine(verse.Id + " - " + verse.Text);
		}

		// Print a famous verse using a multi-field lookup
		var john316 = verseManager.GetByReference("John", 3, 16);
		Console.WriteLine(john316.Id + " - " + john316.Text);

		// Print a range of verses using a range query
		foreach (var verse in verseManager.GetByBookAndVerseRange("Eph", 6, 10, 6, 18)) {
			Console.WriteLine(verse.Id + " - " + verse.Text);
		}

		// Practice some delete and save operations
		var rev2219 = verseManager.GetById("Rev22:19");
		verseManager.DeleteById(rev2219.Id); // Remove a verse
		Console.WriteLine("DELETED - " + rev2219.Text);
		verseManager.Save(rev2219); // On second thought, after reading the verse, maybe we had better put it back. :)
		// Maybe we could just add some extra ones instead?
		verseManager.Save(new Verse() { Id="Opn2:21", Book = "Opinions", Chapter = 2, Number = 21, Text = "God helps those who help themselves." }); // Was actually Ben Franklin
		verseManager.Save(new Verse() { Id = "Fab6:5", Book = "Fabrications", Chapter = 6, Number = 5, Text = "God doesn't care what we call him, as long as we believe in something." }); // Not true at all.
		// Actually Rev22:18 says that's a bad idea too, so maybe we just stick straight to the true.
		verseManager.DeleteById("Opn2:21");
		verseManager.DeleteById("Fab6:5");

		var personManager = new RecordManager<Person>(); // Finally show an example of an on-the-fly RecordManager
		// Demonstrate saving some array properties
		var phillip = new Person() { Id = "Phillip Markert", FavoriteVerses = new string[] { "Col3:23", "Rom8:28", "Rev3:20" } };
		personManager.Save(phillip);
		personManager.Save(new Person() { Id = "Ryan Caskey", FavoriteVerses = new string[] { "John3:16", "Eph2:8", "Eph2:9" } });
			
		// Now demonstrate multi-valued ID lookup (closest equivalent to a 1-Many Join), uses a $in clause
		Console.WriteLine("Some of Phillip's favorite verses are...");
		foreach (var verse in verseManager.GetManyByIds(phillip.FavoriteVerses)) {
			Console.WriteLine(verse.Id + " - " + verse.Text);
		}

		// Find people who have John 3:16 in their favorite-verses array.
		foreach (var person in personManager.AsQueryable.Where(p => p.FavoriteVerses.Contains("John3:16"))) {
			Console.WriteLine(person.Id + " likes John 3:16");
		}

		Console.ReadKey();
	}

	private static IEnumerable<Verse> parseVerses(StreamReader sr) {
		// Iterator to return verses as long as we keep finding more.  Loads about 31,000 verses.
		while (!sr.EndOfStream) {
			var match = Regex.Match(sr.ReadLine(), @"(?<Reference>(?<Book>\d*[A-Za-z]+)(?<Chapter>\d+):(?<Verse>\d+))\s(?<Text>.+$)");
			if (match.Success) {
				yield return new Verse() { Id = match.Groups["Reference"].Value, Book = match.Groups["Book"].Value, Chapter = int.Parse(match.Groups["Chapter"].Value), Number = int.Parse(match.Groups["Verse"].Value), Text = match.Groups["Text"].Value };
			}
		}
	}
}