-
-
Notifications
You must be signed in to change notification settings - Fork 198
Tutorial
The full source of the tutorial can be found in the source code in the samples directory here: https://github.com/sebastienros/yessql/tree/master/samples/YesSql.Samples.Hi
The model which will be used during this tutorial is made of a single class `BlogPost`. Here is the complete class:public class BlogPost
{
public string Title { get; set; }
public string Author { get; set; }
public string Content { get; set; }
public DateTime PublishedUtc { get; set; }
public string[] Tags { get; set; }
}
This class contains different content types in order to demonstrate that we don't have to care about them during the modeling process. There is no constraint on them. As a document, it's a root aggregate, which means any related object will be persisted as only one document. In YesSql a document will be materialized as a single database record in the Document
table.
A store is just a simple relational database instance. It can be any type of SQL database, as long as NHibernate supports it. The tutorial will use Microsoft Sql Ce 4.0 but you can switch it to anyone you want.
A store is represented by the Store
class. An instance of Store
should be unique per application, a singleton. This is a common practice in database management. If you have ever used NHibernate or Entity Framework the you will find it familiar. It depends on each application to define what strategy is used to access this singleton (DI, static accessor, ...). This tutorial is based on single method call, which means it won't deal with this particular aspect.
var store = new Store().Configure(MsSqlCeConfiguration.MsSqlCe40.ConnectionString("Data Source=Store.sdf"));
In this example the store is initialized using a connection string to a local Sql Ce database named Store.sdf
.
The store will behave as a factory for database transactions. In YesSql they are called __sessions__. This is the same terminology as in NHibernate.At this stage you will obviously need to add a reference to
System.Data.SqlServerCe
if you want to run the application.
// creating a blog post
var post = new BlogPost
{
Title = "Hello YesSql",
Author = "Bill",
Content = "Hello",
PublishedUtc = DateTime.UtcNow,
Tags = new[] {"Hello", "YesSql"}
};
// saving the post to the database
using(var session = store.CreateSession())
{
session.Save(post);
session.Commit();
}
A call to session.Commit()
is mandatory in order to commit the transaction. Otherwise the changes would not be taken into account. This is a key principle in YesSql to provide transaction commits.
// loading a single blog post
using(var session = store.CreateSession())
{
var p = session.QueryDocument<BlogPost>(query => query.FirstOrDefault());
Console.WriteLine(p.Title); // > Hello YesSql
}
Using the QueryDocument
method doesn't give access to the inner properties of the documents themselves, which is a major limitations for most real scenarios. To do such queries, you will need to create some dedicated indexes.
- Mapped indexes, to do elementary queries on document properties
- Reduced indexes, for grouping and doing queries on aggregated property values
public class BlogPostByAuthor : MapIndex
{
public virtual string Author { get; set; }
}
Properties are virtual
because it will be mapped in NHibernate to a specific table with the same name as the class.
public class BlogPostIndexProvider : IndexProvider<BlogPost>
{
public override void Describe(DescribeContext<BlogPost> context)
{
// for each BlogPost, create a BlogPostByAuthor index
context.For<BlogPostByAuthor>().Map(blogPost => new BlogPostByAuthor { Author = blogPost.Author });
}
}
This class will tell the system how to construct the indexes from an existing BlogPost
object. Right now there is only one index defined, but several could be described in the same provider.
Then the provider is registered in the system like this:
store.RegisterIndexes<BlogPostIndexProvider>();
// loading blog posts by author
using (var session = store.CreateSession())
{
var ps = session.QueryByMappedIndex<BlogPostByAuthor, BlogPost>(
query => query.Where(x => x.Author.StartsWith("B"))
);
foreach (var p in ps)
{
Console.WriteLine(p.Author); // > Bill
}
}
This example demonstrates how to use the index to query BlogPost
by their Author
property.
Let's say we want to compute some indexes representing how many '`BlogPost`' are published for a specific day, and also keep a trace of those posts. Here is a `BlogPostByDay` index class inheriting from `ReduceIndex`.
public class BlogPostByDay : ReduceIndex
{
[GroupKey]
public virtual string Day { get; set; }
public virtual int Count { get; set; }
}
public class BlogPostByDay : ReduceIndex
{
[GroupKey]
public virtual string Day { get; set; }
public virtual int Count { get; set; }
}
The difference with a MapIndex
is that the key grouping all documents is marked with an attribute. It's useful to let YesSql automatically group object for you, and also to do lookups in the database for existing index entries.
This index then needs to be described in an index provider. The only difference is that on top of a map function, it also need to define how to reduce those results. Ultimately a delete function tells YesSql what to do with the index when a related object is deleted, the opposite of reducing the index.
// for each BlogPost, aggregate in an exiting BlogPostByDay
context.For<BlogPostByDay, string>()
.Map( blogPost => new BlogPostByDay {
Day = blogPost.PublishedUtc.ToString("yyyyMMdd"),
Count = 1
})
.Reduce( group => new BlogPostByDay {
Day = group.Key,
Count = group.Sum(p => p.Count)
}
.Delete( (index, map) => {
index.Count -= map.Sum(x => x.Count);
// if Count == 0 then delete the index
return index.Count > 0 ? index : null;
});
// loading blog posts by day of publication
using (var session = store.CreateSession())
{
var ps = session.QueryByReducedIndex<BlogPostByDay, BlogPost>(
query => query.Where(x => x.Day == DateTime.UtcNow.ToString("yyyyMMdd"))
);
foreach (var p in ps)
{
Console.WriteLine(p.PublishedUtc); // > [Now]
}
}
// counting blog posts by day
using (var session = store.CreateSession())
{
var days = session.QueryIndex<BlogPostByDay>().ToList();
foreach (var day in days)
{
Console.WriteLine(day.Day + ": " + day.Count); // > [Today]: 1
}
}