NoSQLRavenDB在观念C/S应用下的一点执行

RavenDB介绍

RavenDB是一个基于.NET开发的NoSQL数据库。下边是合法介绍的一个大概翻译:

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是一个用.NET编写的事务性开源文档数据库,提供灵活的数据模型,设计用来缓解来自实事求是世界系统的需求。

RavenDB允许你连忙而神速地构建高性能、低顺延的应用程序。

更多介绍可以浏览官方网站的介绍:http://ravendb.net/features

现象介绍

由于NoSQL一般是用来Web场景,比如Web应用程序(尤其MVC
Web应用程序),或者Web服务(包括REST服务等)。目前,需要实现一个简便的数目编辑工具,但是是因为某些原因,那一个工具必须和一个桌面的Windows
Forms应用程序集成在同步,且也要满意两个用户同时操作数据的要求。对于这种专业的C/S格局的采纳,能否接纳RavenDB这样的NoSQL来作为Server端的数据库呢?

答案当然是可以的。毕竟RavenDB本身就襄助二种运行情势:嵌入情势(Embedded)和服务器格局(Server)。对于C/S的采取,很自然就是把RavenDB部署在一个服务器上,运行于Server情势,然后在客户端通过.NET
Client API来访问。

赶上题目

在这么些C/S应用程序中使用RavenDB的历程中,遭逢的最大的题目,如故RavenDB本身的有的特性所带动的界定,分别为:

  1. 历次得到的数据量有限量。RavenDB规定每回得到的多寡量默认为128条,最多可配置为1024条。对于自己这多少个工具的数据量,就是5000条左右,其实只要拔取任何数据库技术的话(比如Entity
    Framework),且也在局域网内,完全可以五遍性载入到内存中。但是使用RavenDB就必须考虑分页处理。
  2. 各样Session可以调用的次数有限制。RavenDB规定每个Session调用服务端的最大次数是30,并且推荐最好控制在1次左右。由于有诸如此类的确定,就无法在全方位客户端应用程序的生存期内维持一个共享的session。
    对于EF也不设有这么的限制。
  3. 检索是基于Lucene的。对于字符串进行Contain操作会出错,这是由于对于类似的全文检索,RavenDB都是借助于Lucene的。由此需要事先定义搜索的目录,并选用单独的Search方法。
  4. RavenDB内置的Lucene分词器对于华语的支撑有问题。就需要单独使用其他中文分词器。

釜底抽薪措施

针对以上的限定,并构成自己这多少个C/S小工具的有些表征,使用了如下解决措施:

  1. 结合BindingNavigator和BindingSource,编写了一个活动分页的工具类(RavenDBDataSource),可以让BindingNavigator的前后导航按钮实现分页导航,仍可以补助标准过滤(Where)和全文检索(Search)后的分页。具体用法见下“RavenDBDataSource解析和用法”。
  2. 即便不可以维系一个共享的Session,不过足以维持一个共享的Store对象,在每便需要获取数据或更新数据的时候,创设单独的Session。不过需要小心的是,由于并未共享Session,会造成后面取回的数量丢失变更跟踪,需要协调举行跟踪与付出。见下边的“咋样保存数据变动”。
  3. 本人从Lucene.NET的网站下载了Contri包,直接动用了中间的“Lucene.Net.Analysis.Cn.ChineseAnalyzer”,即把Lucene.Net.Contrib.Analyzers.dll文件放到RavenDB\Server\Analyzers目录里面。把自然有趣味的校友也可以行使ICTCLAS的Lucene实现。
  4. 预定义全文搜索索引的话,我的措施是在一连数据库后,检查是否留存所需索引,不存在就用代码成立。当然也足以透过Studio来成立。见下”创立索引”。

RavenDBDataSource解析和用法

代码见:https://github.com/heavenwing/redmoon/blob/master/RavenDBDataSource.cs

本条类提供了一个结构器public RavenDBDataSource(IDocumentStore store,
BindingNavigator bn, BindingSource bs),可以承受IDocumentStore
、BindingNavigator 和BindingSource
作为参数。其中会对bn举办局部开端化处理。

提供了一个重载的Load方法,可以无参数,或者收受Func<IRavenQueryable<T>,
IRavenQueryable<T>> criteria, string indexName =
“”三个参数。criteria用来对查询举行布局,indexName顾名思义,在拓展Search操作的时候就需要传入预先定义的目录的称呼。在Load方法中,会对调用代码构造好的询问举行实践,依据PageSize的安装进行分页查询,把询问结果赋值给BindingSource来指示和BindingSource绑定的控件(如DataGridView)举行刷新。在举办分页查询的同时,也会更新当前的页码。

中间BindingNavigator
对象的PositionItem的TextChanged事件处理,会触发Load事件。为了避免频率过高的执行,我动用了一个自定义的事件延迟器(见:https://github.com/heavenwing/redmoon/blob/master/DelayEvent.cs),当然也足以采用RX来拓展延期。

切实用法就很粗略:实例化一个用于具体实体类的RavenDBDataSource,然后调用Load方法,在Load方法中社团查询。如:

        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
                   );
            }
        }

上述代码中,可以而且对六个特性举办过滤(Where),也可经过设定索引名称(index1Name)对一个或四个特性进行查找(Search)。

除此以外,为了便于五回性取得某个实体的具有数据,这多少个类额外提供了一个艺术public
static List<T> LoadAll(IDocumentSession session, int
pageSize),可以由外部提供一个session以便对得到的具备数据都进行转移跟踪。用法如下:

 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();
                    }

怎么着保存数据变动

对于C/S的运用,可能会需要平常举办保存操作,因此在RavenDB的限量条件下,无法维持一个共享的Session,由于尚未共享的Session,导致不能够对现阶段展示到UI的数目开展改动跟踪,由于没有改变跟踪,对数码举行封存就只有利用如下两种模式的一种:

  1. 比方得以拿走到某个实体的实例,比如BindingSource的某条数据,那么可以使用session.Store(process,
    id)来保存,并调用SaveChanges;
  2. 假使不得不拿到到实体的id,那么只好先Load实体的实例对象,对内部的属性进行编制,并调用SaveChanges;
  3. 假设不得不获得到实体的id,且实体相对相比较庞大(或者不想先Load)的话,可以行使Patching
    API
    举行一些更新。

注意,以上用到的id并不是实业本身的Id属性,以ProcessEntity为例,是var id
= string.Format(“ProcessEntities/{0}”, process.Id);

对此上述两种办法的选项,首选第1种,而有的更新由于不会归到事务中在SaveChanges中联合交由,所以一般不被推荐。

除此以外在如此的限定条件下对于删除操作,可以运用如下两种方法:

  1. NoSQL,先通过id来Load实体的实例对象,然后利用session.Delete(entity)删除,并调用SaveChanges;
  2. 抑或采取session.Advanced.Defer(new DeleteCommandData { Key = id
    })来删除,并调用SaveChanges;

对此删除而言,优选第2种艺术,次选第1种艺术,毕竟Defer方法的的确实施,是要放权SaveChanges中集合交由的,且不用去加载实体的始末。

开创索引

本身的办法是和谐用代码来成立,成立一个措施,在store最先化后,就调用,代码应该一目领悟:

        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}
                    }
                });
            }
        }
网站地图xml地图