MongoDB系列(二):C#应用
前言
上一篇文章《MongoDB系列(一):简介及安装》已经介绍了MongoDB以及其在window环境下的安装,这篇文章主要讲讲如何用C#来与MongoDB进行通讯。再次强调一下,我使用的MongoDB版本是2.6,因为2.6是我最熟悉的版本,而且我使用的GUI工具Robomongo目前还不支持3.0版本。
添加官方驱动
官方驱动可以从Nuget上获取,但是这里我们不使用最新的驱动,而是使用1.9.2这个版本,个人认为该版本对MongoDB2.6的支持最好,而且目前的下载量也是最多。驱动地址:https://www.nuget.org/packages/mongocsharpdriver/1.9.2。因此,需要在程序包管理器中获取Nuget。
打开“程序包管理器中”
输入指令Install-Package mongocsharpdriver -Version 1.9.2,下载添加驱动
连接字符串
mongodb://[username:password@]host1[:port1][,host2[:port2],…[,hostN[:portN]]][/[database][?options]]
内容 |
描述 |
mongodb:// |
是连接字串必须的前缀字串 |
username:password@ |
可选项,连接到数据库后会尝试验证登陆 |
host1 |
必须的指定至少一个host |
:portX |
可选项,默认连接到27017 |
/database |
如果指定username:password@,连接并验证登陆指定数据库。若不指定,默认打开admin数据库。 |
?options |
是连接选项。如果不使用/database,则前面需要加上/。所有连接选项都是键值对name=value,键值对之间通过&或;(分号)隔开 |
C#驱动提供的常用API
方法 |
描述 |
InsertBatch |
批量插入 |
Insert |
单条插入 |
FindOneById |
按Id查询 |
Save |
保存,如果库中有记录则更新,否则做插入,按Id匹配 |
Remove |
删除指定文档 |
AsQueryable |
返回IQueryable<T>对象 |
Update |
更新一个或多个文档 |
RemoveAll |
删除所有记录 |
… |
其它 |
代码说明
抽象实体类Entity
public abstract class EntityWithTypedId<TId>
{
public TId Id { get; set; }
} public abstract class Entity : EntityWithTypedId<ObjectId>
{
}
MongoDB要求每个集合都需要有一个Id,即使你定义的类中没有Id字段,存数据的时候也会生成一个Id,而且Id的类型默认是使用ObjectId,当然也可以使用其他简单类型作为Id,如int。
核心代码封装DbContext
public class DbContext
{
private readonly MongoDatabase _db; public DbContext()
{
var client = new MongoClient("mongodb://localhost:27017");
var server = client.GetServer();
_db = server.GetDatabase("Temp");
} public MongoCollection<T> Collection<T>() where T : Entity
{
var collectionName = InferCollectionNameFrom<T>();
return _db.GetCollection<T>(collectionName);
} private static string InferCollectionNameFrom<T>()
{
var type = typeof(T);
return type.Name;
}
}
1. 通过连接字符串与数据库建立连接。
2. 获取需要操作的Database,这里是Temp。
3. 类名与Collection名一致,作为映射的约束。如果库中没有这个Collection,则创建该Collection,如果有,则操作该Collection。
定义一个股票类Stock,包含股票代码,股票名称,股票价格等简单类型字段以及股票粉丝复杂字段:
public class Stock : Entity
{
public string Symbol { get; set; } public string Name { get; set; } public double Price { get; set; } public List<Follower> Followers { get; set; }
} public class Follower
{
public string Name { get; set; } public int Age { get; set; }
}
代码调用
static void Main()
{
SetConvention(); var db = new DbContext();
var collection = db.Collection<Stock>(); var stocks = new List<Stock>
{
new Stock
{
Symbol = "",
Name = "股票1",
Price = ,
Followers = new List<Follower>
{
new Follower{ Name = "张三", Age = },
new Follower{ Name = "李四", Age = },
new Follower{ Name = "王五", Age = }
}
},
new Stock
{
Symbol = "",
Name = "股票2",
Price = ,
Followers = new List<Follower>
{
new Follower{ Name = "张三", Age = },
new Follower{ Name = "李四", Age = }
}
},
new Stock
{
Symbol = "",
Name = "股票3",
Price = ,
Followers = new List<Follower>
{
new Follower{ Name = "张三", Age = }
}
},
new Stock
{
Id = ObjectId.GenerateNewId(), //这里可以自己设定Id,也可以不设,不设的话操作后会自动分配Id
Symbol = "",
Name = "股票4",
Price =
}
}; Console.WriteLine("批量插入");
var results = collection.InsertBatch(stocks);
Console.WriteLine(results.Count()); //这里返回的是1,挺奇怪的。
Console.WriteLine(); var stock = new Stock
{
Id = ObjectId.GenerateNewId(), //这里可以自己设定Id,也可以不设,不设的话操作后会自动分配Id
Symbol = "",
Name = "股票5",
Price =
}; Console.WriteLine("单条插入");
var result = collection.Insert(stock);
Console.WriteLine("插入是否成功:{0}", result.Ok);
Console.WriteLine(); Console.WriteLine("通过Id检索");
var findedStock = collection.FindOneById(BsonValue.Create(stock.Id));
Console.WriteLine("Symbol:{0}, Name:{1}, Price:{2}", findedStock.Symbol, findedStock.Name, findedStock.Price);
Console.WriteLine(); Console.WriteLine("保存操作,库里有数据");
stock.Symbol = "";
result = collection.Save(stock);
Console.WriteLine("保存是否成功:{0}", result.Ok);
Console.WriteLine(); Console.WriteLine("删除");
result = collection.Remove(Query<Stock>.EQ(n => n.Id, stock.Id));
Console.WriteLine("删除是否成功:{0}", result.Ok);
Console.WriteLine(); Console.WriteLine("保存操作,库里没数据");
result = collection.Save(stock);
Console.WriteLine("保存是否成功:{0}", result.Ok);
Console.WriteLine(); Console.WriteLine("简单查询");
var list = collection.AsQueryable().Where(n => n.Price >= ).ToList();
Console.WriteLine("查询结果条数:{0}", list.Count);
Console.WriteLine(); Console.WriteLine("复杂类型查询");
list = collection.AsQueryable().Where(n => n.Followers.Any(f => f.Name == "王五")).ToList();
Console.WriteLine("查询结果条数:{0}", list.Count);
Console.WriteLine(); Console.WriteLine("批量更新");
var query = Query<Stock>.Where(n => n.Price >= );
var update = Update<Stock>.Set(n => n.Name, "股票300")
.Set(n => n.Price, ); result = collection.Update(query, update, UpdateFlags.Multi);
Console.WriteLine("批量更新是否成功:{0}", result.Ok);
Console.WriteLine(); Console.WriteLine("批量删除");
result = collection.Remove(Query<Stock>.Where(n => n.Price >= ));
Console.WriteLine("批量删除更新是否成功:{0}", result.Ok);
Console.WriteLine(); Console.WriteLine("删除所有记录");
result = collection.RemoveAll();
Console.WriteLine("删除所有记录是否成功:{0}", result.Ok);
Console.WriteLine(); Console.ReadKey();
}
全局公约设置
private static void SetConvention()
{
var pack = new ConventionPack {new IgnoreExtraElementsConvention(true), new IgnoreIfNullConvention(true)};
ConventionRegistry.Register("IgnoreExtraElements&IgnoreIfNull", pack, type => true);
}
1. IgnoreExtraElementsConvention:忽略库中有但是类中没有定义的字段。这个一般用于敏感字段处理,例如密码字段,它会存在用户Collection中,但是这个字段只是登录校验的时候会用到(这时可以用js来查询),其他用户查询(linq查询)基本都不需要用到密码字段。
2. IgnoreIfNullConvention:如果字段null,则不存这个字段,简单来说就是省空间,假设一个类中有A,B两个字段,其中A字段为空,如果指定该设置,存为:{B:'B'},否则,存为{A:null, B:'B'}。
返回值说明
为什么MongoDB提供的API基本都有返回值?那如果API中出现的异常怎么处理,被MongoDB吃掉了?
这里我查看了MongoDB的驱动源码,它的结果是通过执行getLastError的方式来获取的,这是c++的方式,C#无法捕捉到这些异常,因此,返回值标识着操作是否成功。同时,API中也会抛出C#的异常或者自定义的异常。这就说明了,操作要满足两个条件才算成功:一、无异常,二、返回值标识成功。
Repository方式
来到这里,应该有很多人会问,为什么还要用Repository?特别是接触过Entity Framework,因为Entity Framework表明已包含了Unit of Work和Repository,或者是看过《博客园的大牛们,被你们害惨了,Entity Framework从来都不需要去写Repository设计模式》这类文章的童鞋。
首先,我需要表明立场,对于不使用Repository的观点,我是百分之八九十赞同的。那为什么还要用呢?不为什么,我就是任性(开个玩笑)!我认为做技术的不能太偏执,还是要根据具体的场景和需求,技术和框架没有绝对好的,只有相对好的。技术是发展的,但技术不可能面面俱到。
那么为什么要用Repository呢?因为我要写单元测试,我需要通过Mock的方式抛开数据库访问的依赖,要Mock的话,要通过接口或虚方法(virtual)。现在的EF 6确实包含了Repository的思想,但是直接用dbContext的话,还是无法Mock(如果可Mock,请告之),因此需要用Repository来包装一下。就好像当你需要测试一个internal的类的时候,你需要定义一个public的类包一下这个内部类。
因此,如果你需要写单元测试的话,那么你应该需要Repository,否则,觉得怎么爽就怎么用吧!
通用接口IRepository
public interface IRepositoryWithTypedId<T, in TId> where T : EntityWithTypedId<TId>
{
IEnumerable<bool> InsertBatch(IEnumerable<T> entities);
bool Insert(T entity);
T Get(TId id);
bool Save(T entity);
bool Delete(TId id);
IQueryable<T> AsQueryable();
bool RemoveAll();
} public interface IRepository<T> : IRepositoryWithTypedId<T, ObjectId> where T : Entity
{ }
通用实现MongoRepository
public class MongoRepositoryWithTypedId<T, TId> : IRepositoryWithTypedId<T, TId> where T : EntityWithTypedId<TId>
{
private readonly MongoCollection<T> _collection; public MongoRepositoryWithTypedId()
{
var client = new MongoClient("mongodb://localhost:27017");
var server = client.GetServer();
var db = server.GetDatabase("Temp");
var collectionName = InferCollectionNameFrom();
_collection = db.GetCollection<T>(collectionName);
} private string InferCollectionNameFrom()
{
var type = typeof(T);
return type.Name;
} protected internal MongoCollection<T> Collection
{
get { return _collection; }
} public IEnumerable<bool> InsertBatch(IEnumerable<T> entities)
{
var result = Collection.InsertBatch(entities);
return result.Select(n => n.Ok);
} public bool Insert(T entity)
{
var result = Collection.Insert(entity);
return result.Ok;
} public T Get(TId id)
{
return Collection.FindOneById(BsonValue.Create(id));
} public bool Save(T entity)
{
var result = Collection.Save(entity);
return result.Ok;
} public bool Delete(TId id)
{
var result = Collection.Remove(Query<T>.EQ(t => t.Id, id));
return result.Ok;
} public IQueryable<T> AsQueryable()
{
return Collection.AsQueryable();
} public bool RemoveAll()
{
var result = Collection.RemoveAll();
return result.Ok;
}
} public class MongoRepository<T> : MongoRepositoryWithTypedId<T, ObjectId>, IRepository<T> where T : Entity
{ }
股票接口IStockRepository
public interface IStockRepository : IRepository<Stock>
{
bool UpdateBatch(double minPrice, string name, double price); bool DeleteBatch(double minPrice);
}
注:如果通用方法足够用的话,可不需要自定义接口,直接使用IRepository<T>。 如IRepository<Stock> repository = new MongoRepository<Stock>();
股票接口实现StockRepository
public class StockRepository : MongoRepository<Stock>, IStockRepository
{
public bool UpdateBatch(double minPrice, string name, double price)
{
var query = Query<Stock>.Where(n => n.Price >= minPrice);
var update = Update<Stock>.Set(n => n.Name, name)
.Set(n => n.Price, price); var result = Collection.Update(query, update, UpdateFlags.Multi);
return result.Ok;
} public bool DeleteBatch(double minPrice)
{
var result = Collection.Remove(Query<Stock>.Where(n => n.Price >= minPrice));
return result.Ok;
}
}
代码调用
static void Main()
{
SetConvention(); var repository = new StockRepository(); var stocks = new List<Stock>
{
new Stock
{
Symbol = "",
Name = "股票1",
Price = ,
Followers = new List<Follower>
{
new Follower{ Name = "张三", Age = },
new Follower{ Name = "李四", Age = },
new Follower{ Name = "王五", Age = }
}
},
new Stock
{
Symbol = "",
Name = "股票2",
Price = ,
Followers = new List<Follower>
{
new Follower{ Name = "张三", Age = },
new Follower{ Name = "李四", Age = }
}
},
new Stock
{
Symbol = "",
Name = "股票3",
Price = ,
Followers = new List<Follower>
{
new Follower{ Name = "张三", Age = }
}
},
new Stock
{
Id = ObjectId.GenerateNewId(), //这里可以自己设定Id,也可以不设,不设的话操作后会自动分配Id
Symbol = "",
Name = "股票4",
Price =
}
}; Console.WriteLine("批量插入");
var results = repository.InsertBatch(stocks);
Console.WriteLine(results.Count());
Console.WriteLine(); var stock = new Stock
{
Id = ObjectId.GenerateNewId(), //这里可以自己设定Id,也可以不设,不设的话操作后会自动分配Id
Symbol = "",
Name = "股票5",
Price =
}; Console.WriteLine("单条插入");
var result = repository.Insert(stock);
Console.WriteLine("插入是否成功:{0}", result);
Console.WriteLine(); Console.WriteLine("通过Id检索");
var findedStock = repository.Get(stock.Id);
Console.WriteLine("Symbol:{0}, Name:{1}, Price:{2}", findedStock.Symbol, findedStock.Name, findedStock.Price);
Console.WriteLine(); Console.WriteLine("保存操作,库里有数据");
stock.Symbol = "";
result = repository.Save(stock);
Console.WriteLine("保存是否成功:{0}", result);
Console.WriteLine(); Console.WriteLine("删除");
result = repository.Delete(stock.Id);
Console.WriteLine("删除是否成功:{0}", result);
Console.WriteLine(); Console.WriteLine("保存操作,库里没数据");
result = repository.Save(stock);
Console.WriteLine("保存是否成功:{0}", result);
Console.WriteLine(); Console.WriteLine("简单查询");
var list = repository.AsQueryable().Where(n => n.Price >= ).ToList();
Console.WriteLine("查询结果条数:{0}", list.Count);
Console.WriteLine(); Console.WriteLine("复杂类型查询");
list = repository.AsQueryable().Where(n => n.Followers.Any(f => f.Name == "王五")).ToList();
Console.WriteLine("查询结果条数:{0}", list.Count);
Console.WriteLine(); Console.WriteLine("批量更新");
result = repository.UpdateBatch(, "股票300", );
Console.WriteLine("批量更新是否成功:{0}", result);
Console.WriteLine(); Console.WriteLine("批量删除");
result = repository.DeleteBatch();
Console.WriteLine("批量删除更新是否成功:{0}", result);
Console.WriteLine(); Console.WriteLine("删除所有记录");
result = repository.RemoveAll();
Console.WriteLine("删除所有记录是否成功:{0}", result);
Console.WriteLine(); Console.ReadKey();
}
注:我这里没有提供Unit of Work的实现,因为我认为MongoDB对连接的处理比关系型好,当然用Unit of Work的话应该会更好。
源码下载
下载地址:https://github.com/ErikXu/MongoDBUsage
MongoDB系列(二):C#应用的更多相关文章
- MongoDB系列二(介绍).
一.特点 学习一个东西,至少首先得知道它能做什么?适合做什么?有什么优缺点吧? 传统关系型数据库,遵循三大范式.即原子性.唯一性.每列与主键直接关联性.但是后来人们慢慢发现,不要把这些数据分散到多个表 ...
- MongoDB系列二
简介 MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql ...
- MongoDB 系列(二) C# 内嵌元素操作 聚合使用
"_id" : "639d8a50-7864-458f-9a7d-b72647a3d226","ParentGuid" : "00 ...
- MongoDB系列二:MongoDB安装过程
一.MongoDB安装,以Linux系统安装为例:(下载:www.mongodb.org 注意使用stable版本) 1.下载最新版本的MongoDB安装包,wget http://fastdl.mo ...
- windows下mongodb基础玩法系列二CURD附加一
windows下mongodb基础玩法系列 windows下mongodb基础玩法系列一介绍与安装 windows下mongodb基础玩法系列二CURD操作(创建.更新.读取和删除) windows下 ...
- windows下mongodb基础玩法系列二CURD操作(创建、更新、读取和删除)
windows下mongodb基础玩法系列 windows下mongodb基础玩法系列一介绍与安装 windows下mongodb基础玩法系列二CURD操作(创建.更新.读取和删除) windows下 ...
- MongoDB系列之二(主动复制)
目前我正在进行MongoDB的双机热备方面相关的工作.根据我目前看到的MongoDB方面的材料,MongoDB的实际部署有三种方式,分别是“主动复制”,“副本集”以及“分片副本集”. 首先我们从最简单 ...
- mongoDB系列之(二):mongoDB 副本集
1. 什么是副本集 副本集就是mongoDB副本所组成的一个集群. 同期原理是,写操作发生在主库,从库同步主库的OpLog日志. 集群中没有特定的主库,主库是选举产生,如果主库down了,会再选举出一 ...
- 前端构建大法 Gulp 系列 (二):为什么选择gulp
系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...
随机推荐
- SQL Server表分区
什么是表分区 一般情况下,我们建立数据库表时,表数据都存放在一个文件里. 但是如果是分区表的话,表数据就会按照你指定的规则分放到不同的文件里,把一个大的数据文件拆分为多个小文件,还可以把这些小文件放在 ...
- java: web应用中不经意的内存泄露
前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...
- SSH实战 · 唯唯乐购项目(中)
用户模块 三:一级分类的查询 创建一级分类表并导入基本数据 CREATE TABLE `category` ( `cid` int(11) NOT NULL AUTO_INCREMENT, ` ...
- 使用webstorm+webpack构建简单入门级“HelloWorld”的应用&&引用jquery来实现alert
使用webstorm+webpack构建简单入门级"HelloWorld"的应用&&构建使用jquery来实现 1.首先你自己把webstorm安装完成. 请参考这 ...
- CENTOS 6.5 平台离线编译安装 Mysql5.6.22
一.下载源码包 http://cdn.mysql.com/archives/mysql-5.6/mysql-5.6.22.tar.gz 二.准备工作 卸载之前本机自带的MYSQL 安装 cmake,编 ...
- H5坦克大战之【玩家控制坦克移动】
自从威少砍下45+11+11的大号三双之后,网上出现了各种各样的神级段子,有一条是这样的: 威少:Hey,哥们,最近过得咋样! 浓眉:对方开启了好友验证,请先添加对方为好友 威少:...... JRS ...
- 写出易调试的SQL
h4 { background: #698B22 !important; color: #FFFFFF; font-family: "微软雅黑", "宋体", ...
- 自己来实现一个简易的OCR
来做个简易的字符识别 ,既然是简易的 那么我们就不能用任何的第三方库 .啥谷歌的 tesseract-ocr, opencv 之类的 那些玩意是叼 至少图像处理 机器视觉这类课题对我这种高中没毕业的人 ...
- 【SAP业务模式】之ICS(三):前台操作
本片博文开始讲解SAP前台是如何实现ICS业务模式的. 一.VA01开立销售订单 我这里为了方便,创建了一个订单类型ZMIV作为公司间销售的订单类型,其实公司间销售订单跟标准的销售订单是一致的.同时, ...
- linux-图形化远程管理协议
远程管理控制方式: RDP(remote desktop protocol)协议: telnet: SSH(Secure Shell): RFB(Remote FrameBuffer)协议(图形化远程 ...