RavenDB in Practice, Part 2 - Using the Client API
This series is about how to start using RavenDB without any prior knowledge about Document Databases. There is assumption that you’re familiar with Relational Databases like Sql Server, though.
In the introduction post of this series I briefly examined what is a Document Database, what is a document and how RavenDB Management Studio looks like. Now it’s time to show you how to actually use RavenDB in your applications. So let’s talk about the client API.
The target of this post is to show you how to use the client API: we’ll start by creating a basic model entity, than we’re going to store an instance of this entity in RavenDB database, then load a entity and update it. Finally, I’m going to show you how to do some more complex queries.
You can use RavenDB from any language or platform (more on that in a future blog post). In this blog post we’re going to use the .NET client API which let you consume RavenDB from any .NET application very easily.
Let’s create an empty ASP.NET MVC application and add the following the RavenDB NuGet package to it:
After the RavenDB NuGet package has been installed you’re ready to start use RavenDB from the MVC application that we created. But we need first need to make sure that we have a RavenDB database to connect to. In order to that, run the RavenDB console application as we did in the previous post, and edit the connection string in the web.config to point to the correct server URL:
<connectionStrings> <add name="RavenDB" connectionString="Url=http://localhost:8080" /> </connectionStrings>
Now that we have RavenDB and the Client API set up, it’s time to do something interesting with it. In order to start interacting with RavenDB with the Client API you’ll need to create an instance of the IDocumentSession and the IDocumentStore. The session is define the boundaries of your Unit Of Work, which will be in a typical web application the entire web request, and the IDocumentStore lets you create sessions. It’s a good practice to create one instance of IDocumentStore per application and one session per unit of work.
Showing how to effectively managing the session in a web application is out of the scope of this blog post (which require us talking us to talk about IoC patterns), but you can take a look on the RaccoonBlog project in order to see one simple approach to achieve this. The RaccoonBlog project was created specifically for demonstration purposes in this blog posts series and it’s now the blog engine that we use to run this blog).
For this demonstration we’re going to create the session and the document store directly in the controller. As specified in the above paragraph, in real applications you’ll typically want to manage the session out of the controller.
public class ProductController : Controller { private static readonly IDocumentStore documentStore; private IDocumentSession _session; static ProductController() { documentStore = new DocumentStore { ConnectionStringName = "RavenDB" }.Initialize(); } protected override void OnActionExecuting(ActionExecutingContext filterContext) { _session = documentStore.OpenSession(); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { _session.SaveChanges(); _session.Dispose(); } }
Now that we have the session in place, we can start using it to do CRUD operations on the database. Consider the following Product entity which we’re going to use in this blog post:
public class Product { public string Id { get; set; } public string CategoryId { get; set; } public string SupplierId { get; set; } public string Name { get; set; } public string Code { get; set; } public decimal StandardCost { get; set; } public decimal ListPrice { get; set; } public int UnitsOnStock { get; set; } public int UnitsOnOrder { get; set; } public bool Discontinued { get; set; } public string PhotoFile { get; set; } public DateTime CreatedAt { get; set; } }
Let’s create an instance of this entity and store it into the database:
public ActionResult StoreSomeProductInDatabase() { var product = new Product { Name = "Product Name", CategoryId = "category/1024", SupplierId = "supplier/16", Code = "H11050", CreatedAt = DateTime.Now, StandardCost = 250, ListPrice = 189, FilePhoto = "path to picture.jpg", }; _session.Store(product); _session.SaveChanges(); return Content(product.Id); }
First we’re creating a product and storing it in the session. I said we’re storing it in the session because when you call the _session.Store(product) method it wouldn’t send the product to the database yet. In order to actually save the product in the database you’ll need to call the _session.SaveChanges(). Note that after you call the _session.SaveChanges() method we can use the product.Id, which will be “products/1”. We didn’t assign that Id manually but let the client API assigns it to us automatically. If you’ll look on the RavenDB console application you’ll see the following log output:
Raven is ready to process requests. Build 385, Version 1.0.0.0 / 64f1188
Server started in 2,519 ms
Data directory: C:\Users\Fitzchak\Downloads\RavenDB-Unstable-Build-385\Server\Data
HostName: <any> Port: 8080, Storage: Esent
Server Url: http://fitzchak-pc:8080/
Press <enter> to stop or 'cls' and <enter> to clear the log
Request # 1: GET - 26 ms - <default> - 404 - /docs/Raven/Replication/Destinations
Request # 2: GET - 0 ms - <default> - 404 - /docs/Raven/Hilo/products
Request # 3: PUT - 241 ms - <default> - 201 - /docs/Raven/Hilo/products
Request # 4: POST - 44 ms - <default> - 200 - /bulk_docs
PUT products/1
At request #4 we can see the actual post request which will store the products/1 document. We got response status #200 which means that the document is successfully stored in the database. In addition you can see that we do not need to query the database and ask what was the generated id for our product (which is a typically case with auto increase field implementation in relational databases), because the Id is generated by the client API (which use the HiLo algorithm to generate it). This “magic” happens in request #2 and #3 which we’ll be covered in the upcoming posts.
You can look on the RavenDB Management Studio in order to see the documents that we just stored:
We could have also store a lot of products in the session and than call the SaveChanges which will insert all of them to the database with just one call:
public ActionResult InsertSomeMoreProducts() { for (int i = 0; i < 50; i++) { var product = new Product { Name = "Product Name " + i, CategoryId = "category/1024", SupplierId = "supplier/16", Code = "H11050" + i, CreatedAt = DateTime.Now, StandardCost = 250 + (i * 10), ListPrice = 189 + (i * 10), }; _session.Store(product); } _session.SaveChanges(); return Content("Products successfully created"); }
This will create 50 documents in the database with just one call to the database, as you can see in the RavenDB console output (I substitute some of the log output with … for space reasons):
Request # 5: POST - 17 ms - <default> - 200 - /bulk_docs
PUT products/2
PUT products/3
PUT products/4
PUT products/5
...
PUT products/49
PUT products/50
PUT products/51
This how it’s looks like on the Management Studio:
Now that we have a few documents in the database it’s time to see how we can load one of them. In order to load the entire document you can use the Load method:
Product product = _session.Load<Product>("products/5"); Product product2 = _session.Load<Product>("5");
The above lines are equivalent, they return the same product. The second line is a handy shortcut that RavenDB Client API provides us, which can come in handy when you expose the int part of the ID as a web parameter like in the following code:
public ActionResult GetProduct(int id) { Product product = _session.Load<Product>(id); return Content(product.Name); }
In the above code we used a number in order to get the document instead of having you use the actual full ID (“products/” + id). Now it’s time to modify the product and save it to the database:
public ActionResult LoadAndUpdateProduct() { Product product = _session.Load<Product>("products/5"); product.ListPrice -= 10; _session.SaveChanges(); return Content("Product 5 successfully updated"); }
As you can see, updating a document is as simple as loading the document, changing its properties and calling the _session.SaveChanges method. This can be happening because that the session keeps track of each of the loaded entities. This lets the session determine which of the documents has been updated and send just them to the database.
What is left is to delete a document. Consider the following code which will do exactly that:
public ActionResult DeleteProduct(int id) { Product product = _session.Load<Product>(id); if (product == null) return HttpNotFound("Product {0} does not exist"); _session.Delete(product); _session.SaveChanges(); return Content(string.Format("Product {0} successfully deleted", id)); }
One interesting thing that we do here is to check if product is null. This is very important step after loading a document because that _session.Load method will return null if there is no document with the corresponding specified id in the database.
Until now we did plain CRUD operations. Now it’s time to create some more complex queries... Raven Client API comes with a great support for Linq so we can use this in order to create more complex queries. Here in an example of how we can get all the products that are available for sale (discontinued equal to false) ordered by the product’s list price:
public ActionResult GetDiscontinuedProducts() { var products = from product in _session.Query<Product>() where product.Discontinued == false orderby product.ListPrice select product; return View(products.ToList()); }
As you can see, RavenDB Client API gives you what you’ll expect from a modern database client API to support. It has great LINQ provider that you’ll let you do almost anything you can do with a regular LINQ queries.
In this post I demonstrated how easy is to use the RavenDB .NET Client API in order to store some documents in the database, load them, update and remove them. Than we saw that we can use LINQ powered queries in order to actually query the database with some criteria and order by statement.
In the future blog posts I’m going to talk about the following topics:
- Introduce the RaccoonBlog project and blog about the interesting parts of it.
- Talk about modeling entities in a way that suits documents database.
If you have any feedback / questions / suggestions regards this blog posts I’ll be more than happy to hear it.
Comments
How does RavenDB handle entities with private/readonly properties? Does it support access to private fields like NHibernate does?
phlange, You have to do a bit of extra work, but it can be done, see http://ravendb.net/faq/immutable-entities for details
Phalange, By default, it will handle private/public properties and public fields. You can tell it to handle private fields as well
How do you tell it to handle private fields? And is it possible to use an access policy like camelcase underscore?
Yes, you can modify the contract resolver to do those sort of things. The entry point is: documentStore.Conventions.JsonContractResolver
which is the best way to deploy web application with ravendb ?
What happens if I add a new property to product class or remove existing property after saving it in RavenDB?
whitestream, It just works, the new property will be added/removed accordingly
@ phalange / ayende:
DocumentStore.Conventions.JsonContractResolver = new CamelCasePropertyNamesContractResolver();
I am not sure this is fully supported. Tried enabling it and all my Raven LINQ queries break
The amount of typos makes this nearly unreadable, but still a good intro into the API, so thank you!