使用 Web API 2 和 MongoDb






4.60/5 (10投票s)
使用 Web API 2 和 MongoDb
引言
我正在为几位同事写一篇快速教程,帮助他们开始使用 MongoDb,并决定与大家分享。
我将展示如何构建一个小应用程序来记录我软件中的某些特定活动,并删除了部分复杂性,使其保持简单易读,但这并不影响您的 POCO 类有多少个属性,是 2 个还是 20 个。
基本上,这是一个循序渐进的教程。那么,开始吧!
- 访问 MongoDb 网站:https://mongodb.ac.cn/ 并下载适合您需求的 MongoDb zip 文件。在本例中,我下载了 64 位 Windows 版本。
- 解压文件,您会得到几个文件夹。
- 在 bin 文件夹旁边添加一个 data 文件夹。
- 选择 bin 文件夹,按住 Shift 键并右键单击,然后选择“在此处打开命令窗口”。
- 输入:mongod --dbpath ../data
最后一步是告诉 Mongo 数据存储的路径,文档将被保存在那里。
完成这些步骤后,在 Visual Studio 中添加一个新的 Web API 项目。
- 添加 Domain 文件夹用于领域模型。
请注意,我通常会在这里使用一个类库项目。但对于这个快速入门,我将所有内容都设置在一个项目中。
回到解决方案
- 在 Domain 文件夹内添加一个 Repository 文件夹。这个文件夹将用于存放仓库契约(或者如果您愿意,也可以是接口)。
- 添加 services 文件夹,用于服务控制器。
- 添加 repositories 文件夹,用于仓库实现。
- 添加一个空的 Web API 控制器。
再次说明,这里我将不使用任何 IoC 进行依赖注入。所以我将按需实例化所有需要的对象。
此时,您的解决方案已设置好,MongoDb 也已启动。所以让我们从 Repository 模式开始,并在 domain/repositories 中添加以下 IRepository
:
namespace m2a.Domain.Repositories
{
public interface IRepository<T>
{
T Add(T entity);
IEnumerable<T> GetAll();
}
}
这个通用仓库,在大多数情况下,将包含所有 CRUD 操作,除非您有任何特定需求。
接下来是 ILogActivityRepository
,您可以在这里添加任何特定的日志活动函数。在本例中,没有特别之处。
namespace m2a.Domain.Repositories
{
public interface ILogActivityRepository:IRepository<LogActivity>
{
}
}
然后,添加一个 domain
类,正如您所见,我的 Domain
对象是 POCO,它们只表示我的领域模型,没有其他。对于任何业务规则验证,我使用 Specification 模式来干净地完成。但这又是另一个话题,您可以阅读 我关于它的文章。
namespace m2a.Domain
{
public class LogActivity
{
public string ActivityName{ get; set; }
public string Message{ get; set; }
}
}
然后在 repository 文件夹中,添加 repository 的实现。
namespace m2a.Repositories
{
public class LogActivityReppository:ILogActivityRepository
{
private MongoCollection _logActivitiesCollection;
public LogActivityReppository(MongoDatabase logActivity)
{
if (logActivity == null) throw new NullReferenceException("Mongodatabase cannot be null. Please check your settings!");
_logActivitiesCollection = logActivity.GetCollection<LogActivity>("activities");
}
public LogActivity Add(LogActivity la)
{
la.Id = ObjectId.GenerateNewId().ToString();
_logActivitiesCollection.Insert(la);
return la;
}
public IEnumerable<LogActivity> GetAll()
{
return _logActivitiesCollection.FindAllAs<LogActivity>();
}
}
}
这里是 LogActivityService
,它继承自 ServiceBase
。以下代码片段中的命名空间将指示这些对象在 Visual Studio 解决方案中应该放在哪里。
namespace m2a.Services
{
public class LogActivityService : ServiceBase
{
private LogActivityRepository _repo;
public LogActivityService()
{
_repo = new LogActivityRepository(GetDB());
}
internal IEnumerable<LogActivity> GetAll()
{
return _repo.GetAll();
}
internal void Add(LogActivity la)
{
_repo.Add(la); ;
}
}
}
namespace m2a.Services
{
public class ServiceBase
{
public MongoDatabase GetDB()
{
var db = new MongoConfig();
return db.Database;
}
}
}
在这里,请注意 DI 是通过 LogActivityReppository
构造函数注入的。这意味着我的服务层绑定到了 Mongo,这不是一个好的实践。
如果您有任何 IoC 来处理这些依赖项,那也没关系。但如果没有,请将 MongoDb 的相关代码移至仓库。它属于那里。所以,将 ServiceBase
重命名为 RepositoryBase
并将其移至 Repositories 文件夹非常简单,您就完成了!
现在,让我们添加一个控制器来暴露 Get
和 Post
方法,以便能够向 MongoDb 插入一个文档并将其取回。
namespace m2a.Controllers
{
public class LogActivitiesController : ApiController
{
private LogActivityService _service;
public LogActivitiesController()
{
_service = new LogActivityService();
}
// GET api/logactivities
public IEnumerable<LogActivity> Get()
{
var res = _service.GetAll();
return res;
}
// POST api/logactivities
public LogActivity Post([FromBody]LogActivity la)
{
return _service.Add(la);
}
}
}
请注意,当您将一个对象传递给方法时,Post
中的 [FromBody]
不是必需的,因为对象默认就在 HTTP 请求的正文中。
对于原始类型,它是必需的,因为它们默认在头部,这就是为什么我们不指定 [FromUri]
。同样,所有这些约定都遵循 MVC 的原则:约定优于配置。
最后,我没有使用 Fiddler 来添加活动,而是编写了两个单元测试,一个用于将活动添加到 MongoDb,另一个用于将其取回,以确保活动已存储并成为一个文档。
请注意,此测试仅用于添加一个值,并非真正的单元测试,因为它会访问数据库(因此是一个集成测试)。我建议为您的单元测试使用模拟框架,以避免任何减慢速度或向数据库添加任何不需要的数据。
[TestMethod]
public void should_succeed_if_logactivity_id_is_set_to_Mongodb_objectId()
{
var lac = new LogActivitiesController();
var la = new LogActivity
{
ApplicationName = "m2a website will be updated one day!",
ApplicationId = "2",
ApplicationPath = "m2a.ca",
ApplicationServer = "",
ClassName = "yeah",
Id = "",
LogDate = DateTime.Now,
UserId = 22,
Username = "m2a",
BusinessFunctionName="adding a quote",
Parameters="name, id, date, others"
};
var res= lac.Post(la);
Assert.IsTrue(res.Id != "");
}
[TestMethod]
public void should_succeed_if_there_is_any_activity()
{
var lac = new LogActivitiesController();
var res= lac.Get();
Assert.IsNotNull(res);
}
就是这样!您已经拥有一个 MongoDb 数据库和 Web API 2,可以添加或获取一个活动。
当然,这仅仅是一个开始,但您可以定义任何复杂的对象图,数据将被序列化并存储为 Mongo 文档。顺便说一句,在 MongoDb 中,一切都是文档,我鼓励您在这里阅读更多关于它的信息:https://mongodb.ac.cn/
现在,如果您查看我的 serviceBase
类,您会注意到我实例化了一个 MongoConfig
,它创建一个 mongo
数据库对象,如下所示:
public class MongoConfig
{
public MongoDatabase Database;
public MongoConfig()
{
var client = new MongoClient(Settings.Default.m2aConnectionString);
var server = client.GetServer();
Database = server.GetDatabase(Settings.Default.m2aDatabaseName);
}
}
您可能会说,如果每次调用服务时都要实例化这个配置,那性能会不会有问题?
是的!您说得对!我对此思考了很久,因为我一直认为这个配置应该像 NHibernate 的会话一样:这是一个成本很高的操作,应该在应用程序启动时只执行一次。RavenDb
,另一个文档数据库,也像 NHibernate 一样工作,这就是为什么(和我一起工作过的许多人一样)我感到困惑。
但对于 MongoDb 来说情况并非如此,因为 Mongocsharpdriver 在后台做了魔法。它缓存所有对象,因此您无需担心。
SQL vs NoSQL
当您手中已经有 SQL Server 数据库,并且有许多变体(如可以在项目中嵌入的 localdb 或 compact 版本)时,.NET 开发人员为什么应该使用 NoSQL 数据库?
在我看来,最重要也是第一个原因是“阻抗不匹配”。我不会深入探讨这一点,但我可以举一个例子来说明我的观点。
我有一个客户对象,其中包含一个 Address
和一个 List
的 Orders
。Order
本身包含一个 product
列表,如下所示:
public class Client
{
public string Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
public class Address
{
public string Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
}
public class Order
{
public string Id { get; set; }
public DateTime DateTime { get; set; }
public IEnumerable<Product> Products { get; set; }
}
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
在这里,在关系数据库中,客户数据将存储在 3 个表中:client
、address
和 Order
。Order
数据也将至少存储在两个表中。您需要某种工厂来映射数据,以便能够通过您的对象访问它。这种差异就是阻抗不匹配。
第二个原因是文档数据库大多数时候采用 JSON 格式(MongoDb 是 Bson 格式),这意味着它们不与任何语言或技术绑定,可以提取或操作。这就是为什么有些人使用 JavaScript 库来构建非凡的应用程序。
希望这为您探索更多 MongoDb 提供了一个起点。还有一些不错的客户端应用程序可以访问 MongoDb 文档。我尝试了 MongoVue,它是免费的,而且非常好用。