wenmo8 发布的文章

我在之前很多文章里面,介绍过Winform主界面的开发,基本上都是标准的界面,在顶部放置工具栏,中间区域则放置多文档的内容,但是在顶部菜单比较多的时候,就需要把菜单分为几级处理,如可以在顶部菜单放置一二级菜单,这种方式在一般功能点不算太多的情况下,呈现的界面效果较为直观、也较为美观。不过随着一些系统功能的增多,这种方式可能就会显得工具栏比较拥挤,那么我们是否可以在左侧放置一个树形列表,这样通过树形列表的收缩折叠,就可以放置非常多的菜单功能了。

1、菜单的树形列表展示

一般情况下,树形列表的显示可以分为多个节点,节点可以收缩也可以展开,当然节点是有不同的图标的了。这样就可以把很多功能点整合在一个树列表里面了,树的节点也可以分为很多级别,很多层次


如果我们想按照业务的范畴来区分,也可以分为多个模块展示,类似选项卡的方式,一个模块的功能菜单列表集合在一起展示,如下所示。

上面这样的折叠展示,有利于业务范畴的区分,并且可以让树菜单菜单不会很大,是一种比较好的界面组织方式。

2、菜单的动态配置
管理

上面介绍了树形菜单的展示,以及如何组织菜单的内容,做好这些,就为我们奠定了界面菜单组织的雏形了。

那么问题来了,我们一般是需要根据系统创建很多菜单的,如果是能通过配置的方式,这样才能较好的管理这些菜单,而且可以动态给菜单指定权限,实现不同角色用户的权限控制。

那么我们就需要在系统里面引入一个菜单管理模块,实现菜单的配置管理功能,方便我们后面的动态创建菜单操作。

通过菜单的配置,我们可以指定菜单的图标,是否可见,是否展开,权限控制点,以及菜单触发点击后,处理的窗体对象等信息,有了这些基础信息,我们就很方便把菜单在树形列表里面进行合适、美观的展示了。

3、菜单动态构建的实现

前面介绍了,如何在数据库里面对菜单数据进行了存储,这样我们就可以在系统主界面里面,动态的构建属性列表进行菜单的展示操作了。

首先,我们需要在设计时刻对主界面的布局进行一定的设计,放置一些初始化的树形列表,方便查看效果。至于里面的内容,我们可以根据数据库的菜单配置,动态从数据库里面获取菜单信息,在左侧树形列表里面进行构建。

我们可以通过一个辅助类进行菜单的动态创建,如下所示。

        private voidInitToolbar()
{
TreeMenuHelper helper
= new TreeMenuHelper(this, this.nvBarMenu, this.imageList1);
helper.Init();
}

也就是辅助类,传入当前窗体,以及左侧的导航控件等参数后,我们在辅助类里面封装对应的动态构建菜单的逻辑处理。

首先我们动态创建的开始,先要清空原来控件展示的菜单内容,并重新从数据库里面获取,如下代码所示。

            //清空所有导航控件的内容
barControl.Controls.Clear();
barControl.Groups.Clear();
barControl.Items.Clear();
this.imageList.Images.Clear();//限定显示几个导航选项卡 barControl.NavigationPaneMaxVisibleGroups = 3;//约定菜单共有3级,第一级为大的类别,第二级为小模块分组,第三级为具体的菜单 List<MenuNodeInfo> menuList = BLLFactory<SysMenu>.Instance.GetTree(Portal.gc.SystemType);if (menuList.Count == 0) return;

然后我们会对菜单进行遍历,并判断是否具有对应的权限点,如果没有对应的权限,那么对应菜单的子菜单也不会进一步展示。

            //递归遍历所有的菜单,进行分级展示
            foreach (MenuNodeInfo firstInfo inmenuList)
{
//如果没有菜单的权限,则跳过 if (!Portal.gc.HasFunction(firstInfo.FunctionId)) continue;

创建菜单的时候,我们注意到整个菜单项是动态构建的,因此我们需要根据NavBarControl的控件属性,动态构建对应的选项卡NavBarGroup、展示容器NavBarGroup、树形对象TreeView、树形节点TreeNode等内容,如下代码所示。

                TreeView treeView = newTreeView();
treeView.Dock
=DockStyle.Fill;
treeView.ImageList
= this.imageList;
treeView.ItemHeight
= 30;//设置高度,显示更美观 NavBarGroupControlContainer container= newNavBarGroupControlContainer();
container.Size
= new System.Drawing.Size(213, 412);
container.Controls.Add(treeView);
barControl.Controls.Add(container);
//加载图标 this.imageList.Images.Add(LoadIcon(firstInfo.Icon));int index = this.imageList.Images.Count - 1;//最后一个序号 NavBarGroup group= newNavBarGroup();
group.Caption
=firstInfo.Name;
group.ControlContainer
=container;
group.Expanded
= true;
group.GroupClientHeight
= 410;
group.GroupStyle
=NavBarGroupStyle.ControlContainer;
group.LargeImageIndex
=index;
group.SmallImageIndex
=index;
barControl.Groups.Add(group);
//创建一级列表 TreeNode pNode = newTreeNode();
pNode.Text
=firstInfo.Name;
pNode.Tag
=firstInfo.WinformType;
pNode.ImageIndex
=index;
pNode.SelectedImageIndex
=index;
treeView.Nodes.Add(pNode);
//递归创建子列表 AddTreeItems(pNode, firstInfo.Children);

通过递归的方式,我们就很容易递归构建了所有层次的树形菜单,并进行合适的展示了。

菜单的单击事件,我们通过一个函数代码实现对它进行处理就可以了。

                //处理树形菜单的点击操作,如果TAG存在,则解析并加载对应的页面到多文档里面
                treeView.AfterSelect += (sender, e) =>{string tag = e.Node.Tag as string;if (!string.IsNullOrEmpty(tag))
{
LoadPlugInForm(tag);
}
};

这里面就是对它的AfterSelect 事件进行处理,实现我们动态加载窗体对象到多文档界面的处理了。

其中加载窗体是根据菜单配置的选项,动态构建界面出来的,具体分析代码如下所示。

        /// <summary>
        ///加载插件窗体/// </summary>
        private void LoadPlugInForm(stringtypeName)
{
try{string[] itemArray = typeName.Split(new char[] { ',', ';'});string type = itemArray[0].Trim();string filePath = itemArray[1].Trim();//必须是相对路径//判断是否配置了显示模式,默认窗体为Show非模式显示 string showDialog = (itemArray.Length > 2) ? itemArray[2].ToLower() : "";bool isShowDialog = (showDialog == "1") || (showDialog == "dialog");string dllFullPath =Path.Combine(Application.StartupPath, filePath);
Assembly tempAssembly
=System.Reflection.Assembly.LoadFrom(dllFullPath);if (tempAssembly != null)
{
Type objType
=tempAssembly.GetType(type);if (objType != null)
{
LoadMdiForm(
this.mainForm, objType, isShowDialog);
}
}
}
catch(Exception ex)
{
LogTextHelper.Error(
string.Format("加载模块【{0}】失败,请检查书写是否正确。", typeName), ex);
}
}

加载多文档的操作,就是在集合里面判断是否存在,如果没有存在就创建,否则就激活显示即可,具体处理如下所示。

        /// <summary>
        ///唯一加载某个类型的窗体,如果存在则显示,否则创建。/// </summary>
        /// <param name="mainDialog">主窗体对象</param>
        /// <param name="formType">待显示的窗体类型</param>
        /// <returns></returns>
        public Form LoadMdiForm(Form mainDialog, Type formType, boolisShowDialog)
{
Form tableForm
= null;bool bFound = false;if (!isShowDialog) //如果是模态窗口,跳过 {foreach (Form form inmainDialog.MdiChildren)
{
if (form.GetType() ==formType)
{
bFound
= true;
tableForm
=form;break;
}
}
}
//没有在多文档中找到或者是模态窗口,需要初始化属性 if (!bFound ||isShowDialog)
{
tableForm
=(Form)Activator.CreateInstance(formType);//如果窗体集成了IFunction接口(第一次创建需要设置) IFunction function = tableForm asIFunction;if (function != null)
{
//初始化权限控制信息 function.InitFunction(Portal.gc.LoginUserInfo, Portal.gc.FunctionDict);//记录程序的相关信息 function.AppInfo = newAppInfo(Portal.gc.AppUnit, Portal.gc.AppName, Portal.gc.AppWholeName, Portal.gc.SystemType);
}

}
if(isShowDialog)
{
tableForm.ShowDialog();
}
else{
tableForm.MdiParent
=mainDialog;
tableForm.Show();
}
tableForm.BringToFront();
tableForm.Activate();
returntableForm;
}

4、系统界面的总体效果

最后,为了更好理解整个动态菜单的界面效果,贴出几个做好的界面展示图,供参考学习。

1)标准界面的处理方式

2)树形列表界面的处理方式

打开多文档页面后如下所示。

在Winform界面中窗体我们一般使用多文档进行展示,也就是类似一般的选项卡的方式可以加载多个窗体界面。一般来说,我们如果打开新的窗体,给窗体传递参数是很容易的事情,但是在框架层面,一般的窗体是通过动态创建的,一般传入窗体的类型,在多文档集合里面判断,如果存在则激活,如果不存在则创建的方式,所以我们传递参数会碰到一些问题。本文即使介绍如何在这种方式下,给窗体对象传递参数,从而实现相应的数据处理功能。

不管是主体界面中,左侧包含树形列表,还是顶部包含工具栏的情况,都可能涉及打开窗体的时候,传递一些初始化参数,方便窗体的更新显示的情况,这种的处理相对直接传值的方式需要复杂一点,我们可以通过接口、事件的方式进行处理,下面我来介绍一下整个实现的方式。

1、多文档窗体的构建或者激活

在我的Winform开发框架里面,我们加载多文档窗体的时候,都是统一采用一种方式来进行构建不存在或者激活已有窗体的,代码如下所示。

        private void tool_Purchase_ItemClick(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
ChildWinManagement.LoadMdiForm(
this, typeof(FrmPurchase));
}
private void tool_TakeOut_ItemClick(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
ChildWinManagement.LoadMdiForm(
this, typeof(FrmTakeOut));
}
private void tool_StockSearch_ItemClick(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
ChildWinManagement.LoadMdiForm(
this, typeof(FrmStockSearch));
}

而这个LoadMdiForm的函数,主要判断多文档集合里面是否有对应的对象,没有这创建,有则激活显示即可,代码如下所示。

        /// <summary>
        ///唯一加载某个类型的窗体,如果存在则显示,否则创建。/// </summary>
        /// <param name="mainDialog">主窗体对象</param>
        /// <param name="formType">待显示的窗体类型</param>
        /// <param name="json">传递的参数内容,自定义Json格式</param>
        /// <returns></returns>
        public static Form LoadMdiForm(Form mainDialog, Type formType, stringjson)
{
bool bFound = false;
Form tableForm
= null;foreach (Form form inmainDialog.MdiChildren)
{
if (form.GetType() ==formType)
{
bFound
= true;
tableForm
=form;break;
}
}
if (!bFound)
{
tableForm
=(Form) Activator.CreateInstance(formType);
tableForm.MdiParent
=mainDialog;
tableForm.Show();
}

tableForm.BringToFront();
tableForm.Activate();
returntableForm;
}

这种方式构建的多文档界面如下所示。

2、多文档窗体传参数的实现处理

首先,为了实现这个方式,我们需要先创建一个接口,是我们窗体界面的基类,实现这个接口,然后在加载的时候,转换为对应的接口处理就可以了,具体接口代码如下所示。

    /// <summary>
    ///使用ChildWinManagement辅助类处理多文档加载的窗体,在构建或激活后,触发一个通知窗体的事件,方便传递相关参数到目标窗体。///为了更加通用的处理,传递的参数使用JSON定义格式的字符串。/// </summary>
    public interfaceILoadFormActived
{
/// <summary> ///窗体激活的事件处理/// </summary> /// <param name="json">传递的参数内容,自定义JSON格式</param> void OnLoadFormActived(stringjson);
}

这里参数为了通用,我们定义为字符串的JSON内容,方便实现更加强大的参数处理。

修改好这些,我们需要在基类窗体 BaseForm 实现这个增加的接口,如下所示。

    /// <summary>
    ///常规界面基类/// </summary>
    public partial class BaseForm : XtraForm, IFunction, ILoadFormActived

实现这个接口很容易,为了更加方便业务窗体(继承自基类窗体BaseForm),我们提供一个事件进行处理,具体代码如下所示。

    /// <summary>
    ///常规界面基类/// </summary>
    public partial classBaseForm : DevExpress.XtraEditors.XtraForm, IFunction, ILoadFormActived
{
/// <summary> ///定义一个窗体激活后的处理委托类型/// </summary> /// <param name="json"></param> public delegate void FormActiveHandler(stringjson);/// <summary> ///使用ChildWinManagement辅助类处理多文档加载的窗体,在构建或激活后,触发一个通知窗体的事件,方便传递相关参数到目标窗体。///为了更加通用的处理,传递的参数使用JSON定义格式的字符串。/// </summary> public event FormActiveHandler LoadFormActived;

同时,我们实现接口,就是直接调用事件就可以了,具体代码如下所示。

        /// <summary>
        ///窗体激活的事件处理/// </summary>
        /// <param name="json">传递的参数内容,自定义JSON格式</param>
        public virtual void OnLoadFormActived(stringjson)
{
//默认什么也没做//如果需要处理传参数,则在这里处理参数Json即可 if (LoadFormActived != null)
{
LoadFormActived(json);
}
}

这样我们就完成了基类窗体的处理了,前面我们介绍了动态构建加载窗体的时候,是使用了LoadMdiForm的函数,既然我们的接口实现了上面的ILoadFormActived接口,那么我们动态创建或者激活窗体的时候,那么就使用这个接口进行处理一下,以便实现对应事件的处理操作了。因此我们的窗体加载函数修改代码,如下所示。

        /// <summary>
        ///唯一加载某个类型的窗体,如果存在则显示,否则创建。/// </summary>
        /// <param name="mainDialog">主窗体对象</param>
        /// <param name="formType">待显示的窗体类型</param>
        /// <param name="json">传递的参数内容,自定义Json格式</param>
        /// <returns></returns>
        public static Form LoadMdiForm(Form mainDialog, Type formType, stringjson)
{
bool bFound = false;
Form tableForm
= null;foreach (Form form inmainDialog.MdiChildren)
{
if (form.GetType() ==formType)
{
bFound
= true;
tableForm
=form;break;
}
}
if (!bFound)
{
tableForm
=(Form) Activator.CreateInstance(formType);
tableForm.MdiParent
=mainDialog;
tableForm.Show();
}
//窗体激活的时候,传递对应的参数信息 ILoadFormActived formActived = tableForm as ILoadFormActived;
if (formActived != null
)
{
formActived.OnLoadFormActived(json);
}


tableForm.BringToFront();
tableForm.Activate();
returntableForm;
}

还记得我们前面打开一个多文档窗体的代码,就是利用这个接口进行创建或者激活指定类型的窗体的,如下所示。

ChildWinManagement.LoadMdiForm(this, typeof(FrmItemDetail));

那么我们增加了新的函数参数Json后,我们如果需要传递一个指定的参数给对应的窗体,那么就修改下调用即可。例如下面,为了测试,我传入一个动态构建的类信息,然后转换为Json字符串信息给接收窗体,并进行加载窗体。

            //使用自定义参数调用
            var obj = new { ItemNo = "123456789", ItemName = "测试名称"};var param =JsonConvert.SerializeObject(obj, Formatting.Indented);

ChildWinManagement.LoadMdiForm(
this, typeof(FrmItemDetail), param);

前面我们介绍了基类窗体,已经实现定义了一个事件,并对这些通知的接口进行处理,具体如下所示。

那么我们前面加载的
FrmItemDetail
需要做哪些工作呢,就是实现对事件的处理即可,如下所示。

这样我们就能够通过实现对应的事件,把整个通知事件的处理处理完毕了,我们来看看最终的界面效果,如下所示,接收到的窗体事件后,会弹出一个提示对话框在右下角了。

当然实际上我们可以做的更多,如可以传递一些具体的信息,让它在界面上进行显示。

如其中一个客户基于我的Winform开发框架基础上进行的参数传递案例界面如下所示。

本文只是对其中在框架层面对不同多文档窗体的传值进行的一个研究探索和实现,其中的理念是基于常用的接口和事件驱动的方式进行处理,以期达到方便、高效的目的,如果您有更好的建议,也希望多多交流。

在我们做系统开发的时候,都会面临一个安装包制作的问题,如何把我们做好的系统,通过安装包工具整合成一个安装包给客户进行安装。安装包的优势就是一步步安装就可以了,不用复制一大堆文件给客户,还怕缺少那个文件导致系统运行出错。本文主要介绍基于DevExpress的Winform程序安装包的制作内容,包括VS2010和VS2013/V2105的不同处理方式。

我们知道,VS2010是最后一个可以使用微软安装包制作工具的VS版本了,所以后面VS2012/2013/2015版本都需要使用第三方安装包制作工具,微软推荐使用Install Shield打包工具,本文以这两个环境进行介绍安装包的制作出来。

1、VS2010使用微软安装包制作工具

在该版本及之前的VS 开发工具,都是使用微软自带的安装包工具进行打包,方便快捷,虽然也听说过 Install Shield等大名鼎鼎的安装包制作,但是集成的东西,总是感觉方便一些,而且也能够应付大多数的安装包制作情景,不知道为什么微软把它从后续的版本中移除。

不过庆幸VS2010版本还是继续能够使用其内置的安装包制作工具,本小节介绍的就是基于这个内置安装包制作工具做我们的DevExpressWinform程序。

1)准备好待发布的安装文件

做安装包的时候,一般可以使用项目引用的方式来构建(如活动项目输出的方式),不过我们可能需要对生成的DLL进行一下混淆等特殊处理,所以一般我们是创建一个目录来放置需要发布的文件的。如下所示,我在Bin目录下创建一个DeployedMis的目录,用来存储待发布的文件的。

然后我们在Debug或者目录里面,把对应的exe或者DLL文件进行混淆操作,我们一般可以选.NET Reactor混淆工具进行处理一下,如下所示。

然后我们把生成的Debug目录的文件复制一份到DeployedMis目录,并把混淆生成的文件替换原有的文件就可以了。最后顺带捎上汉化文件(如zh-Hans目录里面的文件)、RDLC报表文件、Excel导入模板和其他资源文件,如下图所示。

2)制作VS安装包

目前我们就准备好发布的文件了,接下来的就是要启动安装包的制作方面的事情了,先在VS解决方案里面创建一个VS安装包工程项目,如下所示。

然后设置好安装包的一些基础信息,如公司信息、产品名称等关键信息,也就是在安装包的项目里面打开属性,得到界面如下图所示。

然后我们在文件系统视图里面,创建相关的目录,并加入相关的文件,注意【应用程序文件夹】目录下的文件就是我们安装到客户目录的文件内容,在这里加入一些主程序exe,然后会自动引入一些DLL的文件的,所以最终得到下面的工程界面截图。

我们在exe的主程序上面右键,创建一个快捷方式,设置好图标后,复制到用户桌面就会成为一个快捷方式的连接了。

如果我们需要为程序添加一个卸载快捷方式,那么我们在【应用程序文件夹】里面加入C:\Windows\System32\msiexec.exe的系统文件,然后创建一个它的快捷方式,复制到对应的目录上,如下界面所示。

卸载的快捷键还需要设置一些安装包的产品ID才能进行卸载处理,如下所示。

其中属性需要设置Arguments参数为:-x {CB83AA8B-4504-48FF-BF3B-E269CC3244EA},其中-x后面的参数就是我们安装项目的ProductCode内容。

弄好这些,我们编译整个安装包就可以生成一个符合实际生产需要的安装包了,安装包同时也具有程序的卸载功能快捷键了,在Win10系统中的菜单如下图所示。

有时候,为了方便检查是否主程序的自动引用少了某些文件,我们还可以检查【检测到的依赖项】列表,查核是否少了某些文件,如果都符合,那么就肯定没问题了。

如上图所示,我们看到添加主程序后的自动检测的引用文件,就包含了DevExpress的相关DLL了,这样我们做成安装包后,客户安装这个安装包就可以使用DevExpress的界面内容了,而不需要在客户端完整的安装一套DevExpress组件了。

2、VS2013及以上,使用InstallShield安装包制作工具

在VS2013上,我们就需要使用InstallShield进行安装包制作了,我曾经在之前的博客里介绍过利用InstallShield制作安装包的操作《
基于InstallShield2013LimitedEdition的安装包制作
》,在使用VS2013创建安装包之前,我们需要安装一个InstallShield的版本,其中LimitedEdition是一个可以申请免费账号使用的版本,当然专业版InstallShield是收费,而且费用也不低的了。使用LimitedEdition,我们也可以创建一般的安装包,本文主要介绍基于LimitedEdition版本的安装包制作。

安装完毕LimitedEdition版本后,我们可以在VS的新建项目里面,有一个安装包的创建工程模板了。

在做基于DevExpress的Winfrom系统InstallShield安装包,我们一样要做上面的事情,就是需要准备一个专门的目录用来放置编译并混淆好的DLL,这样我们在InstallShield LimitedEdition加入主程序的时候,会自动引入所需的DLL以及方便添加所需的资源文件了。

InstallShield工具提供了辅助的各个步骤的操作,基本上我们按照要求处理就可以实现了,具体操作指引界面如下所示。

例如,我在云会员系统的客户端安装包,就是使用这个InstallShield工具进行打包的,设置主安装项目的信息如下所示。

在安装包的制作过程中,最为关键的就是所需文件的添加处理了,如果添加正确,就能正常进行编译、安装操作,否则就容易编译过不了。

我们在【Application Files】里面,添加所需的文件,主要添加主程序以及所需的一些文件,相关的引用DLL可以通过依赖进入。操作如下所示。

如果我们单击左边【Files and Folders】,我们就可以更加详细的操作整个安装包的文件和目录内容了。

如可以查看主程序文件的依赖文件操作。

单击上图右键菜单的【Dependencies from scan at build...】的时候,我们可以看到主程序集带入的相关DLL引用,里面有很多DevExpress相关的DLL,我们可以通过检查这个列表,确认是否少了一些特殊的DLL引用,如果少了,我们再添加对应的文件到安装包目录里面就可以了。

接着可以在其他步骤增加快捷方式,如增加启动程序、卸载程序的快捷方式,如下所示。

我们也可以通过【Shortcuts】功能进入更加直观的界面显示,如下所示。

以及图标修改等操作,还有就是可以修改修改默认安装界面,最后可以得到安装包的界面如下所示。

以上就是两种不同方式的安装包制作的过程,希望能够帮助您在使用并发布DevExpress程序的时候有所帮助,也就是如果在VS2010上开发,我们可以使用微软内置的安装包制作工具,如果是VS2012、VS2013、VS2015等版本上,我们就可以使用InstallShield工具进行安装包制作了。两者制作的总体思路和配置信息是差不多的,不过使用界面和方式有所差异,习惯就好。

一个老客户提出这样的需求,希望将SQLServer中的某个表的数据快速复制到SQLite数据库里面以便进行定期的备份处理,数据表的记录大概有50多万条记录,表有100个字段左右,除了希望能够快速做好外,效率是第一位的,他自己测试总是在一两个小时的时间以上。客户提出这样的需求,我我觉得肯定是没有很好的利用事务的特性,否则速度应该会快得多,但是具体能快到什么程度,心里也不太确定。于是按照这个要求,把这样大的表数据复制作为一个案例来进行研究,最终大数据的复制处理,不到20分钟就可以完成全部数据的复制更新处理。本文主要介绍这个需求如何结合实际开发的需要进行处理,达到快速高效的复制数据的目的,并提供相关的实现思路和代码供参考学习。

1、复制数据的需求及开发思路

由于客户是需要做定期的数据备份,因此这样的复制是进行的,因此大数据的复制效率肯定是很重要的,应该尽可能的短时间完成。数据表的记录大概有50多万条记录,表有100个字段左右的需要也是比常规的表数据会多一些,因此需要做好很好的测试,我们根据这样的需求背景,使用一个测试案例来对性能进行测试。

这样多字段的表,数据字段的一一对应,手工肯定是很累的,所以我们使用代码生成工具Database2Sharp来进行快速开发,这样底层的处理我们就可以不用太过关注,而且可以为不同的数据处理,生成不同的数据访问层即可。

在底层我们主要是采用了微软的Enterprise Library的数据库访问模块,因此它能够很好抽象各种数据库的事务,以适应各种不同数据库的事务处理。使用微软的Enterprise Library模块,可以很好支持SQLSever、Oracle、Mysql、Access、SQLite等数据库。

开发框架,常见的分层模式,可以分为UI层、BLL层、DAL层、IDAL层、Entity层、公用类库层等等

框架的基类我们封装了大量的通用性处理函数,包括数据访问层、业务逻辑层的基类,所有的基类函数基本上都带有一个
DbTransaction trans = null
的定义,就是我们可以采用事务,也可以默认不采用事务,是一个可选性的事务参数。

如数据访问接口和基于SQLServer的数据访问类的实现图示如下所示。

在最高级的抽象基类AbstractBaseDAL的数据访问层里面,都有大量关于数据操作和相关事务的接口可以使用,因此我们在底层继承的子类,如果我们处理数据的增删改查等操作,基本上就不需要做任何扩展性代码了,这样很符合我们快速开发的目的。

在框架的整个数据访问层,我们都定义了很多公用的、带有事务参数的接口,如果我们在常规的数据处理里面,使用事务的话,那么也是很方便的事情。使用事务的批量处理,对于SQLite的操作来说,效率是非常明显的,具体可以在我之前的随笔里《
使用事务操作SQLite数据批量插入,提高数据批量写入速度,源码讲解
》可以了解到,他们之间的处理效率是很大差距的。

2、使用代码生成工具生成所需的代码

上面讲到,开发这样的数据复制处理程序,这样多字段的表,数据字段的一一对应,手工肯定是很累的,所以我们使用代码生成工具Database2Sharp来进行快速开发。

因此使用代码生成工具来快速生成所需要的代码,展开数据库后,从数据库节点上,右键选择【代码生成】【Enterprise Library代码生成】就可以生成标准的界面层一下的代码了,由于我们整个案例是非标准的数据复制处理,界面部分不需要利用代码生成工具进行Winform界面的生成的。

生成代码的一步步操作,最后确认一下就可以生成相关的底层代码了

最后我们生成这样的BLL、DAL、IDAL、Entity几个层的项目代码,整个项目的代码各种继承关系已经处理好了,也就具有了基类拥有的增删改查等基础操作了。

我们做两个不同数据库的复制处理操作,关键还是要生成两个不同数据库访问类的代码(也就是生成一个标准的SQLServer后,复制一份代码,修改下继承基类即可实现),如下代码是两个数据访问类的代码,不用增加任何接口即可满足当前项目的需要的了。

最终我们的项目结构如下所示。

3、进行数据复制处理的Winform界面代码逻辑

为了方便整个复制过程的进度展示(很重要),我们设计了进度条以及文字内容,展示处理过程的进度和耗时等信息,最终界面设计如下所示。

整个界面设计利用后台线程的方式对数据复制进行处理,方便及时在界面显示进度而不阻塞界面线程。

具体的界面代码如下所示。

    public partial classFrmMain : Form
{
privateTimeSpan ExecuteTime;private int currentCount = 0;private BackgroundWorker work = new BackgroundWorker();//使用后台线程进行处理,不阻塞界面显示 publicFrmMain()
{
InitializeComponent();
//定义后台线程的处理 work.DoWork +=work_DoWork;
work.WorkerReportsProgress
= true;
work.ProgressChanged
+=work_ProgressChanged;
work.RunWorkerCompleted
+=work_RunWorkerCompleted;
}
//线程完成后通知结束 void work_RunWorkerCompleted(objectsender, RunWorkerCompletedEventArgs e)
{
this.toolStripProgressBar1.Value = 100;this.toolStripProgressBar1.Visible = false;
MessageUtil.ShowTips(
"操作完成");

ShowMessage(
this.toolStripProgressBar1.Value);//完成 }/// <summary> ///在界面显示文本信息/// </summary> /// <param name="percent">完成百分比</param> private void ShowMessage(intpercent)
{
if (this.ExecuteTime != null)
{
this.lblTips.Text = string.Format("[当前完成数量:{0},完成百分比:{1}, 执行耗时:{2}毫秒 | {3}分钟{4}秒]",this.currentCount, percent, this.ExecuteTime.TotalMilliseconds, this.ExecuteTime.Minutes, this.ExecuteTime.Seconds);
}
}
/// <summary> ///报告进度的时候,显示相关的数量、耗时等内容/// </summary> void work_ProgressChanged(objectsender, ProgressChangedEventArgs e)
{
this.toolStripProgressBar1.Value =e.ProgressPercentage;this.statusStrip1.Refresh();

ShowMessage(e.ProgressPercentage);
}
/// <summary> ///后台线程执行的逻辑代码/// </summary> void work_DoWork(objectsender, DoWorkEventArgs e)
{
CopyDataUtil util
= new CopyDataUtil();//使用一个Action的Lamda表达式,执行通知界面处理 util.Start((percent, ts, current) =>{
work.ReportProgress(percent);
this.ExecuteTime =ts;this.currentCount =current;
});
}
private void btnCopy_Click(objectsender, EventArgs e)
{
if(!work.IsBusy)
{
//如果每次要求使用空白数据库测试,那么先删除旧数据库,再复制备份过去即可 string dbfile = Path.Combine(Environment.CurrentDirectory, "localdb.db");string bakfile = Path.Combine(Environment.CurrentDirectory, "db.db");if (this.chkCopyEmptyDb.Checked &&File.Exists(dbfile))
{
File.Delete(dbfile);
File.Copy(bakfile, dbfile,
true);
}
//显示进度条,并异步执行线程 this.toolStripProgressBar1.Visible = true;
work.RunWorkerAsync();
}
}
private void FrmMain_FormClosing(objectsender, FormClosingEventArgs e)
{
//取消注册的相关事件,防止退出的时候出现异常 if(work != null &&work.IsBusy)
{
work.ProgressChanged
-= work_ProgressChanged; //取消通知事件 work.RunWorkerCompleted -= work_RunWorkerCompleted;//取消完成事件 work.Dispose();
}
}
}

在上面的窗体界面代码里面,最为关键的代码就是具体后台进程的处理逻辑,如下代码所示。

        /// <summary>
        ///后台线程执行的逻辑代码/// </summary>
        void work_DoWork(objectsender, DoWorkEventArgs e)
{
CopyDataUtil util
= new CopyDataUtil();//使用一个Action的Lamda表达式,执行通知界面处理 util.Start((percent, ts, current) =>{
work.ReportProgress(percent);
this.ExecuteTime =ts;this.currentCount =current;
});
}

上面的处理逻辑为了方便,把数据的复制内容放到了一个辅助类里面,并在辅助类的Start方法里面传入了界面通知的Action处理函数,这样我们在
CopyDataUtil
处理的时候就可以随时进行消息的通知了。

数据复制的Start方法定义如下所示。

        /// <summary>
        ///开始执行赋值/// </summary>
        public void Start(Action<int, TimeSpan, int>doFunc)
{
StartTime
= DateTime.Now;//计时开始 InternalCopry(doFunc);//处理数据复制逻辑,并执行外部的函数 EndTime= DateTime.Now;//计时结束 }

整个辅助类
CopyDataUtil
类里面定义了两个不同数据库类型的对象,方便数据库的赋值处理操作,并且定义了开始时间,结束时间,这样可以统计总共的耗时信息,如下代码所示。

    /// <summary>
    ///复制数据的处理类/// </summary>
    public classCopyDataUtil
{
//使用一个计时器,对操作记录进行计时 privateDateTime StartTime, EndTime;//SQLServer数据库表对象 private ProductSqlServer sqlserver = null;//SQLite数据表对象 private ProductSqlite sqlite = null;publicCopyDataUtil()
{
//构建对象,并指定SQLServer的数据库配置项 sqlserver = newProductSqlServer();
sqlserver.DbConfigName
= "sqlserver";//构建对象,并指定SQLite的数据库配置项 sqlite = newProductSqlite();
sqlite.DbConfigName
= "sqlite";
}

整个复制数据的逻辑,主要就是基于事务性的处理,按照分页规则,每次按照一定的数量,批量从SQLServer里面取出数据,然后插入SQLite数据库里面,使用事务可以是的SQLite的数据写入非常高效快速,具体代码如下所示。

        /// <summary>
        ///大数据复制的处理逻辑/// </summary>
        /// <param name="doFunc">外部调用的函数</param>
        private void InternalCopry(Action<int, TimeSpan, int>doFunc)
{
//设置主键,并指定分页数量大小,提高检索效率 string primaryKey = "h_id";int pageSize = 1000;
PagerInfo info
= new PagerInfo(){PageSize = pageSize, CurrenetPageIndex =1};//根据数据的总数,取得总页数 int totalPageCount = 1;int totalCount =sqlserver.GetRecordCount();if (totalCount % pageSize == 0)
{
totalPageCount
= totalCount /pageSize;
}
else{
totalPageCount
= totalCount / pageSize + 1;
}
totalPageCount
= (totalPageCount < 1) ? 1: totalPageCount;//利用事务进行SQLite数据写入,提高执行响应效率 DbTransaction trans =sqlite.CreateTransaction();if (trans != null)
{
//根据每页数量,依次从指定的页数取数据 for (int i = 1; i <= totalPageCount; i++)
{
info.CurrenetPageIndex
= i;//设定当前的页面,并进行数据获取 int j = 1;
List
<ProductInfo> list = sqlserver.FindWithPager("1=1", info, primaryKey, false);foreach (ProductInfo entity inlist)
{
//取得当前数量和进度百分比 int current = (i - 1) * pageSize +j;int percent =GetPercent(totalCount, current);//计算程序耗时,执行外部函数进行界面通知 TimeSpan ts = DateTime.Now -StartTime;
doFunc(percent, ts, current);
//执行通知处理//如果不存在主键记录,则写入,否则更新 if (!sqlite.IsExistKey(primaryKey, entity.H_id, trans))
{
sqlite.Insert(entity, trans);
}
else{
sqlite.Update(entity, entity.H_id, trans);
}
j
++;
}
}
trans.Commit();
}
}

至此,整个项目的代码就基本上介绍完毕了,测试整个复制过程,单表50多万的数据,100个字段左右,在开发机器上20分钟不到就复制完成,确实是很不错的成绩了,如果修改为服务器的环境专门做复制处理,肯定速度还会提高不少。

在花了不少时间研究学习了MongoDB数据库的相关知识,以及利用C#对MongoDB数据库的封装、测试应用后,决定花一些时间来总结一下最近的研究心得,把这个数据库的应用单独作为一个系列来介绍,希望从各个方面来总结并记录一下这个新型、看似神秘的数据库使用过程。本文是这个系列的开篇,主要介绍一些MongoDB数据库的基础知识、安装过程、基础使用等方面。

MongoDB是一款由C++编写的高性能、开源、无模式的常用非关系型数据库产品,是非关系数据库当中功能最丰富、最像关系数据库的数据库。它扩展了关系型数据库的众多功能,例如:辅助索引、范围查询、排序等。

MongoDB主要解决的是海量数据的访问效率问题,它作为分布式数据崛起后,使用较多的一款非结构数据库,必然有其值得称道之处,它的主要功能特性如下:

1)面向集合的存储,适合存储对象及JSON形式的数据。
2)动态查询,MongoDB支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
3)完整的索引支持,包括文档内嵌对象及数组。MongoDB的查询优化器会分析查询表达式,并生成一个高效的查询计划。
4)查询监视,MongoDB包含一个监视工具用于分析数据库操作的性能。
5)复制及自动故障转移,MongoDB数据库支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制。复制的主要目标是提供冗余及自动故障转移。
6)高效的传统存储方式,支持二进制数据及大型对象(如图片或视频)。
7)自动分片以支持云级别的伸缩性,自动分片功能支持水平的数据库集群,可动态添加额外的机器。

1、
MongoDB数据库和传统关系数据库的对比

MongoDB数据库有几个简单的概念需要了解一下。

1)MongoDB中的
database
有着和我们熟知的"数据库"一样的概念 (对 Oracle 来说就是 schema)。一个 MongoDB 实例中,可以有零个或多个数据库,每个都作为一个高等容器,用于存储数据。

2)数据库中可以有零个或多个
collections
(集合)。集合和传统意义上的 table 基本一致,可以简单的把两者看成是一样的东西。

3)集合是由零个或多个
documents
(文档)组成。同样,一个文档可以看成是一
row

4)文档是由零个或多个
fields
(字段)组成。,对应的就是关系数据库的
columns

5)
Indexes
(索引)在 MongoDB 中扮演着和它们在 RDBMS 中一样的角色,都是为了提高查询的效率。

6)
Cursors (游标)和上面的五个概念都不一样,但是它非常重要,并且经常被忽视,其中最重要的你要理解的一点是,游标是当你问 MongoDB 拿数据的时候,它会给你返回一个结果集的指针而不是真正的数据,这个指针我们叫它游标,我们可以拿游标做我们想做的任何事情,比如说计数或者跨行之类的,而无需把真正的数据拖下来,在真正的数据上操作。

它们的对比关系图如下所示。

数据在Mongodb里面都是以Json格式方式进行存储的,如下所示是其中的一个记录内容。

{
_id: ObjectID(
'4bd9e8e17cefd644108961bb'),
name:
'Vivek',class : '12th',
subjects: [
'physics', 'chemistry', 'math', 'english', 'computer'],
address: {
house_no:
'12B',
block:
'B',
sector:
12,
city :
'noida',
},
grade: [
{
exam:
'unit test 1',
score:
'60%'},
{
exam:
'unit test 2',
score:
'70%'}

]
}

在过去的很长一段时间中,关系型数据库一直是最主流的数据库解决方案,他运用真实世界中事物与关系来解释数据库中抽象的数据架构。然而,在信息技术爆炸式发展的今天,大数据已经成为了继云计算,物联网后新的技术革命,关系型数据库在处理大数据量时已经开始吃力,开发者只能通过不断地优化数据库来解决数据量的问题,但优化毕竟不是一个长期方案,所以人们提出了一种新的数据库解决方案来迎接大数据时代的到来——
NoSQL(非关系型数据库)
,其中MongoDB数据库就是其中的NoSQL的杰出代表。在大数据时代中,大数据量的处理已经成了考量一个数据库最重要的原因之一。而MongoDB的一个主要目标就是尽可能的让数据库保持卓越的性能,这很大程度地决定了MongoDB的设计。

根据MongoDB官网的说明,
MongoDB的适用场景如下
:

1)网站实时数据:MongoDB非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。

2)数据缓存:由于性能很高,MongoDB也适合作为信息基础设施的缓存层。在系统重启之后,由MongoDB搭建的持久化缓存层可以避免下层的数据源过载。

3)大尺寸、低价值数据存储:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。

4)高伸缩性场景:MongoDB非常适合由数十或数百台服务器组成的数据库。MongoDB的路线图中已经包含对MapReduce引擎的内置支持。


5)对象或JSON数据存储:MongoDB的BSON数据格式非常适合文档化格式的存储及查询。


MongoDB
不适合使用场景如下:
1)高度事务性系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。

2)传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。
3)需要复杂SQL查询的问题。

MongoDB大多数情况下,可以代替关系型数据库实现数据库业务。它更简单更直接、更快速并且通常对应用开发者的约束更少,不过缺乏事务支持需要慎重考虑业务需要。

2、MongoDB数据库的安装及基础使用

MongoDB数据的官网为:https://www.mongodb.org/,当前版本为3.2,可以直接下载安装版本在Linux或者Windows进行安装。

一般在Windows,我们默认安装的路径为C:\Program Files\MongoDB,安装后可以手动创建一个放置数据库和日志文件的目录,一般不要放在C盘就好,如下所示:

创建文件夹d:\mongodb\data\db、d:\mongodb\data\log,分别用来安装db和日志文件,我们以后运行数据库后,这个目录就用来放置我们创建的数据库和日志资源了。

一般我们安装后,为了在命令行方便调用Mongodb的命令,我们可以设置一个全局的路径变量,如下所示。

默认情况下,mongodb的工作模式,是启动一个DOS窗口,运行mongodb的数据库服务,一旦这个DOS窗口关闭,也就停止了相关的服务,在Windows平台,我们可以把它寄宿在Windows服务里面,让它随着系统的启动而启动,也不必因为误关闭窗口而停止了数据库服务了。

通过下面命令行执行数据库服务的处理。

mongod --dbpath "d:\mongodb\data\db" --logpath "d:\mongodb\data\log\MongoDB.log" --install --serviceName "MongoDB"

然后使用命令行启动服务

NET START MongoDB

创建服务并顺利启动成功后,然后就可以在系统的服务列表里查看到了,我们确认把它设置为自动启动的Windows服务即可。

启动后,我们可以在系统【运行】里面直接使用命令mongo打开窗口就可以进行相关的操作了。

上面用了一些常见的命令操作。

  • show dbs    显示数据库列表
  • use dbname    进入dbname数据库,大小写敏感,没有这个数据库也不要紧
  • show collections    显示数据库中的集合,相当于表格
  • db.<collection_name>.find(); 集合查找方法,参考上面的方式,使用pretty()函数是排版更好看的意思。

而其中find方法很强大,可以组合很多条件查询的方式,如下所示:

  • db.collection.find({ "key" : value })    查找key=value的数据
  • db.collection.find({ "key" : { $gt: value } })    key > value
  • db.collection.find({ "key" : { $lt: value } })    key < value
  • db.collection.find({ "key" : { $gte: value } })    key >= value
  • db.collection.find({ "key" : { $lte: value } })    key <= value
  • db.collection.find({ "key" : { $gt: value1 , $lt: value2 } })    value1 < key <value2
  • db.collection.find({ "key" : { $ne: value } })    key <> value
  • db.collection.find({ "key" : { $mod : [ 10 , 1 ] } })    取模运算,条件相当于key % 10 == 1 即key除以10余数为1的
  • db.collection.find({ "key" : { $nin: [ 1, 2, 3 ] } })    不属于,条件相当于key的值不属于[ 1, 2, 3 ]中任何一个
  • db.collection.find({ "key" : { $in: [ 1, 2, 3 ] } })    属于,条件相当于key等于[ 1, 2, 3 ]中任何一个
  • db.collection.find({ "key" : { $size: 1 } })    $size 数量、尺寸,条件相当于key的值的数量是1(key必须是数组,一个值的情况不能算是数量为1的数组)
  • db.collection.find({ "key" : { $exists : true|false } })    $exists 字段存在,true返回存在字段key的数据,false返回不存在字度key的数据
  • db.collection.find({ "key": /^val.*val$/i })    正则,类似like;“i”忽略大小写,“m”支持多行
  • db.collection.find({ $or : [{a : 1}, {b : 2} ] })    $or或 (注意:MongoDB 1.5.3后版本可用),符合条件a=1的或者符合条件b=2的数据都会查询出来
  • db.collection.find({ "key": value , $or : [{ a : 1 } , { b : 2 }] })    符合条件key=value ,同时符合其他两个条件中任意一个的数据
  • db.collection.find({ "key.subkey" :value })    内嵌对象中的值匹配,注意:"key.subkey"必须加引号
  • db.collection.find({ "key": { $not : /^val.*val$/i } })    这是一个与其他查询条件组合使用的操作符,不会单独使用。上述查询条件得到的结果集加上$not之后就能获得相反的集合。

当然还有插入更新的处理语句也是很特别的。

db.student.insert({name:'student1',subject:['arts','music']})

特别是更新操作需要说明一下,支持常规的$set方法(修改)、$unset方法(移除指定的键),还有原子级的$inc方法(数值增减),$rename方法(重命名字段名称)等等,

db.users.update({"_id" : ObjectId("51826852c75fdd1d8b805801")},  {"$set" : {"hobby" :["swimming","basketball"]}} )
db.users.update({"_id" : ObjectId("51826852c75fdd1d8b805801")},{"$unset" : {"hobby" :1 }} )
db.posts.update({"_id" : ObjectId("5180f1a991c22a72028238e4")}, {"$inc":{"pageviews":1}})
db.students.update( { _id: 1 }, { $rename: { 'nickname': 'alias', 'cell': 'mobile' } } 

upsert是一种特殊的更新操作,不是一个操作符。(upsert = up[date]+[in]sert),也就是如果存在则更新,否则就写入一条新的记录操作。这个参数是个布尔类型,默认是false。

db.users.update({age :25}, {$inc :{"age" :3}}, true)

另外,Update可以对Json的集合进行处理,如果对于subject对象是一个集合的话,插入或更新其中的字段使用下面的语句

db.student.update({name:'student5'},{$set:{subject:['music']}},{upsert:true});

如果是记录已经存在,我们可以使用索引数值进行更新其中集合里面的数据,如下所示。

db.student.update({name:'student3'},{$set:{'subject.0':'arts'}});

如果我们先在集合里面增加一个记录,而非替换的话,那么使用
$push
语句,如下面的语句所示。

db.student.update({name:'student3'},{$push:{'subject':'sports'}})

相反,如果要移除集合里面的某个值,使用
$pop
操作符,那么语句如下所示

db.student.update({name:'student3'},{$pop:{'subject':1}});

其中索引为1标识最右边的记录,-1标识为最左边的记录。

另外还可以使用
$pushAll
和$pullAll来增加/移除一个或多个集合记录,如下代码所示。

db.student.update({name:'student3'},{$pushAll:{'subject':['sports','craft']}})
db.student.update({name:
'student3'},{$pullAll:{'subject':['sports','craft']}})

mongodb的数据库的操作还是比较容易理解的,具体可以进一步参考官网里面的介绍。

https://docs.mongodb.org/manual/

https://docs.mongodb.org/getting-started/csharp/client/

http://mongodb.github.io/mongo-csharp-driver/2.2/

http://wiki.jikexueyuan.com/project/the-little-mongodb-book/

3、MongoDB数据库的C#驱动的使用

数据库的C#驱动使用介绍,可以参考:https://docs.mongodb.org/getting-started/csharp/ 当前已更改为
https://docs.mongodb.com/drivers/csharp
,可以下载相关的DLL然后在项目中引用,当前的驱动版本为2.2,一般引入下面几个DLL即可。

    • MongoDB.Bson.dll
    • MongoDB.Driver.dll
    • MongoDB.Driver.Core.dll

也可以使用VS工具的NugGet包进行下载管理,如下所示。

然后在弹出的NugGet程序包管理界面里面搜索mongo,然后添加MongoDB.Driver的数据库驱动就可以使用了。

MongoDB数据库驱动在2.2版本(或者是从2.0开始)好像完全改写了API的接口,因此目前这个版本同时支持两个版本的API处理,一个是基于MongoDatabase的对象接口,一个是IMongoDatabase的对象接口,前者中规中矩,和我们使用Shell里面的命令名称差不多,后者IMongoDatabase的接口是基于异步的,基本上和前者差别很大,而且接口都提供了异步的处理操作。后面我会分别对这两个部分进行详细的介绍,本文基于篇幅的原因,介绍一下两者的简单差异就可以了。

我们以Mongodb的数据库连接字符串mongodb://localhost/local来进行构建

1)旧接口MongoDatabase对象的构建

            var client =new MongoClient(connectionString);var database = client.GetServer().GetDatabase(new MongoUrl(connectionString).DatabaseName);

2)新接口IMongoDatabase对象的构建

            var client =new MongoClient(connectionString);var database = client.GetDatabase(new MongoUrl(connectionString).DatabaseName);

后者已经没有了
GetServer
的接口了。

3)旧接口的查找对象处理

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual T FindByID(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

MongoCollection
<T> collection =GetCollection();return collection.FindOneById(newObjectId(id));
}

3)新接口查找对象的处理

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual T FindByID(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();return collection.Find(s=> s.Id ==id).FirstOrDefault();
}

新接口已经没有了FindOneById等接口了,所有的操作基本上都通过Find方法进行处理。旧接口很多通过Query对象进行条件的查询,新接口又换了一个对象进行过滤条件处理了,总的来说,两个接口差异非常大。

例如旧版本接口的Query使用C#代码如下所示:

        private voidTestQuery()
{
Query.All(
"name", new List<BsonValue> { BsonValue.Create("a"), BsonValue.Create("b") });//通过多个元素来匹配数组 Query.And(Query.EQ("name", "a"), Query.EQ("title", "t"));//同时满足多个条件 Query.Or(Query.EQ("name", "a"), Query.EQ("title", "t"));//满足其中一个条件 Query.EQ("name", "a");//等于 Query.Exists("type");//判断键值是否存在 Query.GT("value", 2);//大于> Query.GTE("value", 3);//大于等于>= Query.In("name", new List<BsonValue> { BsonValue.Create("a"), BsonValue.Create("b") });//包括指定的所有值,可以指定不同类型的条件和值 Query.LT("value", 9);//小于< Query.LTE("value", 8);//小于等于<= Query.Mod("value", 3, 1);//将查询值除以第一个给定值,若余数等于第二个给定值则返回该结果 Query.NE("name", "c");//不等于 Query.Size("name", 2);//给定键的长度 Query.Type("_id", BsonType.ObjectId);//给定键的类型 Query.ElemMatch("children", Query.And( Query.EQ("name", "C3"), Query.EQ("value", "C")));//Query.Nor(Array);//不包括数组中的值//Query.Not("name");//元素条件语句//Query.NotIn("name", "a", 2);//返回与数组中所有条件都不匹配的文档//Query.Where(BsonJavaScript);//执行JavaScript//Query.Matches("Title", str);//模糊查询 相当于sql中like -- str可包含正则表达式 var keyword = "abc";
Query.Matches(
"Title", new BsonRegularExpression("/.*" + keyword + ".*/"));//模糊Like语法//通过正则表达式 1开头 第二位数0~9且只能一位数,也包含20 var queryName = Query.Matches("Name", new BsonRegularExpression("Donma1([0-9]{1,1})$|20"));//查找年龄 >=10 且<=20 var queryAge = Query.And(Query.GTE("Age", 10), Query.LTE("Age", 20));var entityQuery = Query<UserInfo>.EQ(e => e.Name, "wuhuacong");var entityQuery2 = Query<UserInfo>.EQ(e => e.Id, "4B414D000000011613CD");
}

新版本的条件查询,则丢弃了Query这个对象,提供了FilterDefinition<T> 对象的处理,估计是这个可以处理的更好吧,同时新接口全部支持异步的处理操作了。

如插入记录的异步操作代码如下所示。

        /// <summary>
        ///插入指定对象到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        public virtual asyncTask InsertAsync(T t)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");

IMongoCollection
<T> collection =GetCollection();awaitcollection.InsertOneAsync(t);
}

好了,基于篇幅的原因,把后面介绍的C#开发留到下一篇进行介绍,希望本篇文章对大家了解mongodb数据库,以及如何在C#上面使用该数据库提供了一个简要的指引,希望大家多多支持。