📜 ⬆️ ⬇️

Little Things to Remember When Using RavenDB

Good all the time of day. I will talk about RavenDB. For those who do not know what it is, you can see here . In the future, I assume that you know what is at stake.

Brief introduction


RavenDB is a document-oriented database. It is understood that it is flexible, comfortable, fast and there are many more goodies in it ...

And it is.


But with some reservations.
')

The most important thing



The first thing we see when searching for “project architecture with RavenDB” is a phrase - do not work with RavenDb as a relational database. However, this judgment is valid for working with any NoSQL database.

This phrase means that you need to denormalize the data where it is needed. RavenDB pushes us to the decision to store all the necessary information in one entity.

Consider an example.

Suppose we have the following entity.
public class Article { public long Id {get;set;} public string Title {get;set;} public string Content{get;set;} } 

And let's assume that our Article essence may have comments.

 public class Comment { public long Id {get;set;} public string Author {get;set;} public string Content{get;set;} } 

All we need to do to correctly write the article with comments in the database is:
  1. Add Comments property to Article class
  2. Remove Id properties from the comment.

In other words, we do not need to create a separate table for comments, because they are always inside an entity. This is denormalization.

I added the second paragraph simply because in this model we do not need to refer to a separate comment. We may want to get all the comments on the article, or all the comments of some author, but we do not need a separate comment.

By the way, there are no tables in RavenDB. There are collections of entities, and the belonging of the entity to the collection is very simple. But more on that below.

Metadata


It is worth noting that RavenDB stores entities as JSON objects. As a result, they do not have a specific structure, with the exception of some service properties. The main utility property is @metadata. This object contains all the control data of our document: Id inside RavenDB, type on the server (our Article, for example) and many other properties.

The property Raven-Entity-Name is responsible for belonging to the collection. If you change it, the collection in which the object is located also changes. Id does not automatically change.

By the way, identifiers in RavenDB default to the Id property, but you can make any field an identifier and define your own identifier generation strategy. Described in more detail here . You just have to scroll down a bit.

By the way, the important thing I said earlier, but I repeat once again : The relationship between entities is bad. Everything that the entity works with should be in it.
Of course, a situation may arise when you need to determine whether one entity belongs to another, but if you have to do this all the time, ask yourself - are you using RavenDB correctly and do you need it on the project?

Search by entities



Consider a trivial task - we need to get a list of all posts.

 List<Blog> blogs = null; using (var session = store.OpenSession()) { blogs = session.Query<Blog>().ToList(); } 


What happens in this little piece of code?
First, we create a connection to RavenDB (this is trivial).
Secondly, the session gives us exactly 128 first entities that satisfy the condition. Why 128? Because this is the default behavior. In the config, you can increase this value to 1024, but, you see, this is not exactly the behavior that is required.

This is due to the fact that RavenDb strongly recommends using pagination to work with large amounts of data. And it would be cool if this behavior were already registered in the API, but this is not! Instead, you have to write your bike every time for pagination. First we need to find out how many pages there will be, and then pull out a specific one.
Yes, the task is trivial, but annoying.

By the way, here is the code (perhaps, from your point of view, not optimal), which simplifies working with pagination.
 public static int GetPageCount<T> (this IRavenQueryable<T> queryable, int pageSize) { if (pageSize < 1) { throw new ArgumentException("Page size is less then 1"); } RavenQueryStatistics stats; queryable.Statistics(out stats).Take(0).ToArray(); //     . var result = stats.TotalResults / pageSize; if (stats.TotalResults % pageSize > 0) //   { result++; } return result; } public static IEnumerable<T> GetPage<T>(this IRavenQueryable<T> queryable, int page, int pageSize) { return queryable .Skip((page - 1)*pageSize) .Take(pageSize) .ToArray(); } 


However, this is not all.
In the example above, RavenDB gives us entities sorted by the last modified date . This is the Last-Modified property in the @metadata object I mentioned earlier.

An interesting fact is that you cannot sort by Id. An error flies, or nothing happens.
The solution is simple - we create the Created field and sort it by it.

Using RavenDB for Queries


It is worth remembering that the session is limited to 30 requests, after the expiration of this limit an exception occurs when trying to send a request to the database. Thus, the creators of this excellent database in all respects tell us that we should create a separate session for each request. In principle, this is justified, because the session is a UnitOfWork and, as a result, lightweight. But the constant creation of sessions can lead your code to an unreadable form, so you can do something else:
 private IDocumentSession Session { get { if (_session == null) { _session = _store.OpenSession(); } if (_session.Advanced.NumberOfRequests == _session.Advanced.MaxNumberOfRequestsPerSession) { _session.Dispose(); _session = _store.OpenSession(); } return _session; } } 


Using RavenDB in a project


The creator of the aforementioned Ayende Rahien database says: “Use RavenDB at as high a level as possible.” And as an example, access to the database directly from the controller. Perhaps for small projects this is justified. However, I prefer the good old trekhzvenka with unit-testing, so this way is not for me.

My solution is a proxy over the RavenDB session that does what I need.
The main reason for creating this component is the difficulty with the session session. If Load can still be dunk somehow, then Query is almost unreal. While the add-in is very simple.

And one more thing to say about tests with RavenDB. Maybe this happens that you need to check the work with a real database. In this case, use EmbeddableStore.
One of the reasons for using a real database is testing indexes. But indexes in RavenDB are an extensive subject about which it is worth writing separate article. =)

Source: https://habr.com/ru/post/190028/


All Articles