Document directory
- RavenDBDataSource parsing and usage
- How to save data changes
RavenDB Introduction
RavenDB is a NoSQL database developed based on. NET. The following is a simple translation officially introduced:
RavenDB is a transactional, open-source Document Database written in. NET, offering a flexible data model designed to address requirements coming from real-world systems.
RavenDB allows you to build high-performance, low-latency applications quickly and efficiently.
RavenDB is a transactional open-source document database written in. NET. It provides flexible data models and is designed to meet real-world system requirements.
RavenDB allows you to quickly and efficiently build high-performance, low-latency applications.
Introduction to the official website: http://ravendb.net/features
Scenario
NoSQL is generally used in Web scenarios, such as Web applications (especially MVC Web applications) or Web services (including REST services ). Recently, you need to implement a simple data editing tool. For some reason, this tool must be integrated with a desktop Windows Forms Application, it must also meet the needs of multiple users to operate data at the same time. Can NoSQL such as RavenDB be used as a Server-side database for this standard C/S model?
Of course the answer is yes. After all, RavenDB itself supports two operating modes: Embedded mode and Server mode ). For C/S applications, it is natural to deploy RavenDB on a Server, run in Server mode, and then access it through the. NET Client API on the Client.
Problem Encountered
The biggest problem encountered when using RavenDB in this C/S application is the restrictions imposed by some features of RavenDB:
- The amount of data obtained each time is limited. RavenDB specifies that the data size obtained each time is 128 by default, and a maximum of 1024 data records can be configured. The amount of data in my tool is about 5000. In fact, if other database technologies (such as Entity Framework) are used, they can be loaded to the memory at one time in the LAN. However, using RavenDB requires paging.
- The number of calls per Session is limited. RavenDB specifies that the maximum number of server calls per Session is 30, and it is recommended that the maximum number be controlled once. Due to this rule, a shared session cannot be maintained throughout the lifetime of the client application. This restriction does not exist for EF.
- Search is based on Lucene. An error occurs when performing the Contain operation on the string, because RavenDB relies on Lucene for similar full-text searches. Therefore, you need to pre-define the Search index and use a separate Search method.
- RavenDB's built-in Lucene word divider has problems with Chinese support. You need to use other Chinese Word divider separately.
Solution
Based on the above restrictions and some features of my C/S tool, the following solutions are used:
- Combined with BindingNavigator and BindingSource, an automatic paging tool class (RavenDBDataSource) is compiled, which enables the front and back navigation buttons of BindingNavigator to implement paging navigation and supports conditional filtering (Where) and the page after the full-text Search (Search. For specific usage, see "RavenDBDataSource resolution and usage ".
- Although a shared Session cannot be maintained, a shared Store object can be maintained, and a separate Session can be created each time data needs to be obtained or updated. However, it should be noted that, because there is no shared Session, it will lead to the tracking of data loss and changes retrieved before, and you need to track and submit it yourself. See "how to save data changes" below ".
- From Lucene. NET website downloaded the Contri package and directly used the "Lucene. net. analysis. cn. chineseAnalyzer ", that is, Lucene. net. contrib. analyzers. dll files are stored in the directory RavenDB \ Server \ Analyzers. If you are interested, you can also use Lucene of ICTCLAS.
- If the pre-defined full-text search index is used, after connecting to the database, check whether the required index exists. If the index does not exist, use the code to create it. Of course, you can also create it through Studio. See "create Index ".
RavenDBDataSource parsing and usage
See: https://github.com/heavenwing/redmoon/blob/master/RavenDBDataSource.cs for code
This class provides a constructor public RavenDBDataSource (IDocumentStore store, BindingNavigator bn, BindingSource bs), which can take IDocumentStore, BindingNavigator, and BindingSource as parameters. Some bn initialization will be performed.
Provides an overloaded Load method, which can have no parameters, or supports Func <IRavenQueryable <T>, IRavenQueryable <T> criteria, string indexName =. Criteria is used to construct a query. As the name suggests, you need to input a pre-defined index name during the Search operation. In the Load method, the query constructed by the call code is executed. Paging query is performed based on the PageSize setting. The query result is assigned to BindingSource to prompt the control bound to BindingSource (such as DataGridView). When querying by page, the current page number is also updated.
The TextChanged event processing of the PositionItem of the BindingNavigator object triggers the Load event. To avoid too frequent execution, I used a Custom Event delimizer (see: https://github.com/heavenwing/redmoon/blob/master/DelayEvent.cs) and of course I could also use RX for latency.
The specific usage is simple: instantiate a RavenDBDataSource for a specific object class, then call the Load method to construct a query in the Load method. For example:
private void LoadProcessData() { if (_dsProcess == null) _dsProcess = new RavenDBDataSource<ProcessEntity>(_store, bnProcess, bsProcess); var txt = tstbSearchForProcess.Text.ToLower(); if (string.IsNullOrEmpty(txt)) { if (tscbSource.SelectedIndex == 0) { if (tscbRelatedCount.SelectedIndex < 5) _dsProcess.Load(query => query .Where(o => o.RelatedCount == tscbRelatedCount.SelectedIndex) .OrderBy(o => o.ProductName)); else _dsProcess.Load(query => query .Where(o => o.RelatedCount >= 5) .OrderBy(o => o.ProductName)); } else { if (tscbRelatedCount.SelectedIndex < 5) _dsProcess.Load(query => query .Where(o => o.Source == tscbSource.Text && o.RelatedCount==tscbRelatedCount.SelectedIndex) .OrderBy(o => o.ProductName)); else { _dsProcess.Load(query => query .Where(o => o.Source == tscbSource.Text && o.RelatedCount >= 5) .OrderBy(o => o.ProductName)); } } } else { _dsProcess.Load(query => query .Search(o => o.ProductName, txt) .OrderBy(o => o.ProductName), index1Name ); } }
In the above Code, multiple attributes can be filtered (Where) at the same time, or one or more attributes can be searched by setting the index name (index1Name ).
In addition, this class provides an additional public static List <T> LoadAll (IDocumentSession session, int pageSize) method to easily obtain all data of an object at a time ), A session can be provided externally to track changes to all acquired data. The usage is as follows:
using (var session = _store.OpenSession()) { var processes = RavenDBDataSource<ProcessEntity>.LoadAll(session, 512); var products = RavenDBDataSource<ProductEntity>.LoadAll(session, 512); foreach (var process in processes) { var count = 0; foreach (var product in products) { foreach (var dataset in product.Datasets) { if (process.Id == dataset.Id) count++; } } if (process.RelatedCount != count) process.RelatedCount = count; } session.SaveChanges(); }
How to save data changes
C/S applications may need to be stored from time to time. Therefore, a shared Session cannot be maintained under RavenDB restrictions. Because no shared Session exists, as a result, it is impossible to track changes to the data currently displayed on the UI. Because there is no change tracking, there are only one of the following three methods to save the data:
- If you can obtain an object instance, such as a data entry in BindingSource, you can use session. Store (process, id) to save the data and call SaveChanges;
- If only the Object id can be obtained, you can only Load the Instance Object of the object first, edit the attribute, and call SaveChanges;
- If you can only obtain the Object id, and the object is relatively large (or you do not want to Load it first), you can use the Patching API for partial update.
Note that the id used above is not the Id attribute of the object itself. Taking ProcessEntity as an example, it is var id = string. Format ("ProcessEntities/{0}", process. Id );
For the above three methods, the first choice is 1st, and some updates are generally not recommended because they are not submitted in SaveChanges in a transaction.
In addition, the following two methods can be used for delete operations under such restrictions:
- Load the Instance Object of the object by id, Delete the object by session. Delete (entity), and call SaveChanges;
- Or use session. Advanced. Defer (new DeleteCommandData {Key = id}) to delete and call SaveChanges;
For deletion, we recommend 2nd methods and 1st methods. After all, the actual execution of the Defer method must be submitted in SaveChanges without loading the object content.
Create an index
I create a method by using the code. After the store is initialized, the method is called. The Code should be clear at a glance:
const string AnalyzerName = "Lucene.Net.Analysis.Cn.ChineseAnalyzer, Lucene.Net.Contrib.Analyzers, Version=3.0.3.0, Culture=neutral, PublicKeyToken=85089178b9ac3181"; const string index1Name = "ProcessEntities/ByProductName"; const string index2Name = "ProductEntities/ByZhNameAndEnName"; private void SetDocumentIndex() { var index = _store.DatabaseCommands.GetIndex(index1Name); if (index == null) { _store.DatabaseCommands.PutIndex(index1Name, new IndexDefinitionBuilder<ProcessEntity> { Map = processes => from p in processes select new { p.ProductName, }, Indexes = { {o=>o.ProductName,FieldIndexing.Analyzed}, }, Analyzers = { {o=>o.ProductName,AnalyzerName} } }); } index = _store.DatabaseCommands.GetIndex(index2Name); if (index == null) { _store.DatabaseCommands.PutIndex(index2Name, new IndexDefinitionBuilder<ProductEntity> { Map = processes => from p in processes select new { p.EnName, p.ZhName }, Indexes = { {o=>o.EnName,FieldIndexing.Analyzed}, {o=>o.ZhName,FieldIndexing.Analyzed}, }, Analyzers = { {o=>o.EnName,AnalyzerName}, {o=>o.ZhName,AnalyzerName} } }); } }