65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Web API 2 和 MongoDb

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (10投票s)

2014 年 6 月 28 日

CPOL

6分钟阅读

viewsIcon

33073

downloadIcon

20

使用 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 OrdersOrder 本身包含一个 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 个表中:clientaddress OrderOrder 数据也将至少存储在两个表中。您需要某种工厂来映射数据,以便能够通过您的对象访问它。这种差异就是阻抗不匹配。

第二个原因是文档数据库大多数时候采用 JSON 格式(MongoDb 是 Bson 格式),这意味着它们不与任何语言或技术绑定,可以提取或操作。这就是为什么有些人使用 JavaScript 库来构建非凡的应用程序。

希望这为您探索更多 MongoDb 提供了一个起点。还有一些不错的客户端应用程序可以访问 MongoDb 文档。我尝试了 MongoVue,它是免费的,而且非常好用。

© . All rights reserved.