学习如何在ASP.NET Core中集成MongoDB。

WebAPI项目示例

创建WebAPI

建立一个Net6-with-MongoDB.Demo的WebAPI项目。
通过NuGet安装MongoDB.Driver:

  1. Install-Package MongoDB.Driver
  2. Install-Package AutoMapper
  3. Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

一般来说,我们操作MongoDB都会选择这个官方的Driver。

配置MongoDB

首先,请参照之前的文章安装部署好一个MongoDB节点或集群。
然后,手动创建一个数据库BookStoreDB 和 一个集合 Books。

  1. use BookStoreDB
  2. db.CreateCollection('Books')

或者打开Mongo Express,http://localhost:8081/
1664005531942.png
1664005561959.png
最后,预先插入两条测试数据:

  1. db.Books.insertMany([
  2. {'Name':'Design Patterns','Price':54.93,
  3. 'Category':'Computers','Author':'Ralph Johnson',
  4. 'CreatedDate':ISODate("2012-10-02T07:58:51Z"),
  5. 'UpdatedDate':ISODate("2012-10-02T07:58:51Z")},
  6. {'Name':'Clean Code','Price':43.15,
  7. 'Category':'Computers','Author':'Robert C. Martin',
  8. 'CreatedDate':ISODate("2012-10-02T07:58:51Z"),
  9. 'UpdatedDate':ISODate("2012-10-02T07:58:51Z")}])

需要注意的是:这里的连接字符串指向的是一个没有设置用户名密码的MongoDB节点。如果你的MongoDB节点设置了用户名密码 或者 复制集分片集 之类的,请修改为匹配的连接字符串。
然后,创建一个配置项类,也放到Models目录中:

  1. namespace Net6.with.MongoDB.Demo.Models;
  2. public class DatabaseSettings : IDatabaseSettings
  3. {
  4. public string BooksCollectionName { get; set; }
  5. public string ConnectionString { get; set; }
  6. public string DatabaseName { get; set; }
  7. }
  8. public interface IDatabaseSettings
  9. {
  10. string BooksCollectionName { get; set; }
  11. string ConnectionString { get; set; }
  12. string DatabaseName { get; set; }
  13. }

最后,将其加入到IoC容器中控制:

  1. using Microsoft.Extensions.Options;
  2. using Net6.with.MongoDB.Demo.Models;
  3. using Net6.with.MongoDB.Demo.Services;
  4. var builder = WebApplication.CreateBuilder(args);
  5. // AutoMapper Settings
  6. builder.Services.AddAutoMapper(typeof(MappingConfigs));
  7. var appSetting = new AppSetting();
  8. builder.Configuration.GetSection("AppSetting").Bind(appSetting);
  9. //把AppSetting实体注入到容器,方便在构造函数里使用IOptionsSnapshot<AppSetting> options
  10. builder.Services.Configure<AppSetting>(builder.Configuration.GetSection("AppSetting"));
  11. // MongoDB Settings
  12. builder.Services.Configure<AppSetting>(
  13. builder.Configuration.GetSection(nameof(AppSetting)));
  14. builder.Services.AddSingleton<IAppSetting>(sp =>
  15. sp.GetRequiredService<IOptions<AppSetting>>().Value);
  16. // Add services to the container.
  17. builder.Services.AddSingleton<IBookService, BookService>();
  18. builder.Services.AddControllers();
  19. builder.Services.AddEndpointsApiExplorer();
  20. builder.Services.AddSwaggerGen();
  21. var app = builder.Build();
  22. // Configure the HTTP request pipeline.
  23. app.UseSwagger();
  24. app.UseSwaggerUI();
  25. app.UseHttpsRedirection();
  26. app.UseAuthorization();
  27. app.MapControllers();
  28. app.Run();

这里,IBookstoreDatabaseSettings 接口使用单一实例服务生存期在 DI 中注册。在注入时,接口实例时将解析为 BookStoreDatabaseSettings 对象。

添加配置模型

首先,在appSettings.json中添加以下数据库配置:

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft.AspNetCore": "Warning"
  6. }
  7. },
  8. "AllowedHosts": "*",
  9. "AppSetting": {
  10. "SqlServerSetting": {
  11. "Connection": "Server=192.168.3.40;Database=webdemo;User=sa;Password=longfuchu;"
  12. },
  13. "MysqlSetting": {
  14. "Connection": "server=192.168.3.40;port=3306;user=root;password=123456;database=webdemo;charset=utf8;Allow Zero Datetime=true;sslmode=none;Old Guids=true;Allow User Variables=True"
  15. },
  16. "RedisSetting": {
  17. "Connection": "192.168.3.40:6379,password=",
  18. "Database": "test"
  19. },
  20. "RabbitMQSetting": {
  21. "HostName": "localhost",
  22. "UserName": "admin",
  23. "Password": "admin",
  24. "Port": 5672
  25. },
  26. "MongoDbSetting": {
  27. "Connection": "mongodb://192.168.3.40:2717", ///?replicaSet=rs0
  28. "Database": "BookStoreDB",
  29. "CollectionName": "Books"
  30. }
  31. }
  32. }
  1. namespace Net6.with.MongoDB.Demo.Models;
  2. /// <summary>
  3. /// mongodb setting
  4. /// </summary>
  5. public interface IAppSetting
  6. {
  7. /// <summary>
  8. /// SqlServerSetting
  9. /// </summary>
  10. SqlServerSetting SqlServerSetting { get; set; }
  11. /// <summary>
  12. /// MysqlSetting
  13. /// </summary>
  14. MysqlSetting MysqlSetting { get; set; }
  15. /// <summary>
  16. /// RedisSetting
  17. /// </summary>
  18. RedisSetting RedisSetting { get; set; }
  19. /// <summary>
  20. /// RabbitMQSetting
  21. /// </summary>
  22. RabbitMQSetting RabbitMQSetting { get; set; }
  23. /// <summary>
  24. /// MongoDbSetting
  25. /// </summary>
  26. MongoDbSetting MongoDbSetting { get; set; }
  27. /// <summary>
  28. /// KafkaSetting
  29. /// </summary>
  30. KafkaSetting KafkaSetting { get; set; }
  31. }
  32. /// <summary>
  33. /// mongodb setting
  34. /// </summary>
  35. public class AppSetting: IAppSetting
  36. {
  37. /// <summary>
  38. /// SqlServerSetting
  39. /// </summary>
  40. public SqlServerSetting SqlServerSetting { get; set; }
  41. /// <summary>
  42. /// MysqlSetting
  43. /// </summary>
  44. public MysqlSetting MysqlSetting { get; set; }
  45. /// <summary>
  46. /// RedisSetting
  47. /// </summary>
  48. public RedisSetting RedisSetting { get; set; }
  49. /// <summary>
  50. /// RabbitMQSetting
  51. /// </summary>
  52. public RabbitMQSetting RabbitMQSetting { get; set; }
  53. /// <summary>
  54. /// MongoDbSetting
  55. /// </summary>
  56. public MongoDbSetting MongoDbSetting { get; set; }
  57. /// <summary>
  58. /// KafkaSetting
  59. /// </summary>
  60. public KafkaSetting KafkaSetting { get; set; }
  61. }
  62. public class RabbitMQSetting
  63. {
  64. /// <summary>
  65. /// HostName
  66. /// </summary>
  67. public string HostName { get; set; } = "localhost";
  68. /// <summary>
  69. /// Password
  70. /// </summary>
  71. public string Password { get; set; }
  72. /// <summary>
  73. /// Username
  74. /// </summary>
  75. public string UserName { get; set; }
  76. /// <summary>
  77. /// The port to connect on.
  78. /// </summary>
  79. public int Port { get; set; } = 5672;
  80. }
  81. public class RedisSetting
  82. {
  83. /// <summary>
  84. /// connection string
  85. /// </summary>
  86. public string Connection { get; set; }
  87. /// <summary>
  88. /// database name
  89. /// </summary>
  90. public string Database { get; set; }
  91. }
  92. public class MongoDbSetting
  93. {
  94. /// <summary>
  95. /// connection string
  96. /// </summary>
  97. public string Connection { get; set; }
  98. /// <summary>
  99. /// database name
  100. /// </summary>
  101. public string Database { get; set; }
  102. /// <summary>
  103. /// CollectionName
  104. /// </summary>
  105. public string CollectionName { get; set; }
  106. }
  107. public class SqlServerSetting
  108. {
  109. /// <summary>
  110. /// connection string
  111. /// </summary>
  112. public string Connection { get; set; }
  113. }
  114. public class MysqlSetting
  115. {
  116. /// <summary>
  117. /// connection string
  118. /// </summary>
  119. public string Connection { get; set; }
  120. }
  121. public class KafkaSetting
  122. {
  123. /// <summary>
  124. /// Servers
  125. /// </summary>
  126. public string Servers { get; set; }
  127. }

添加实体模型

在WebAPI项目中添加Models目录,并增加 Book 实体类:

  1. using MongoDB.Bson.Serialization.Attributes;
  2. using MongoDB.Bson;
  3. namespace Net6.with.MongoDB.Demo.Models;
  4. public class Book : MongoDocBase
  5. {
  6. [BsonElement("Name")]
  7. public string BookName { get; set; }
  8. public decimal Price { get; set; }
  9. public string Category { get; set; }
  10. public string Author { get; set; }
  11. }
  12. public class MongoDocBase
  13. {
  14. [BsonId]
  15. [BsonRepresentation(BsonType.ObjectId)]
  16. public string Id { get; set; }
  17. [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
  18. public DateTime? CreatedDate { get; set; }
  19. [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
  20. public DateTime? UpdatedDate { get; set; }
  21. }

需要注意的是:MongoDB存储时间类型数据时,都是先转换为UTC时间,然后存储到数据库中。当我们取出存储的时间时,就会出现时差的问题。因此,一般我们会给文档中的日期类型加上如下所示的注解,将它转换为本地时间传输:

  1. [BsonDateTimeOptions(Kind = DateTimeKind.Local)]

在实际应用中,我们会给实体类创建一些DTO,然后在应用层进行DTO向DO的转换。因此,这里我们假设会进行 创建Book 和 修改Book 的操作,创建两个DTO类:
(1)CreateBookDto

  1. using System.Text.Json.Serialization;
  2. namespace Net6.with.MongoDB.Demo.Models;
  3. public class CreateBookDto
  4. {
  5. public string BookName { get; set; }
  6. public decimal Price { get; set; }
  7. public string Category { get; set; }
  8. public string Author { get; set; }
  9. [JsonIgnore]
  10. public DateTime CreatedDate { get; set; } = DateTime.Now;
  11. }

(2)UpdateBookDto

  1. using System.Text.Json.Serialization;
  2. namespace Net6.with.MongoDB.Demo.Models;
  3. public class UpdateBookDto : CreateBookDto
  4. {
  5. public string Id { get; set; }
  6. [JsonIgnore]
  7. public DateTime UpdatedDate { get; set; } = DateTime.Now;
  8. }

创建映射规则配置:

  1. using AutoMapper;
  2. namespace Net6.with.MongoDB.Demo.Models;
  3. public class MappingConfigs : Profile
  4. {
  5. public MappingConfigs()
  6. {
  7. CreateMap<CreateBookDto, Book>().ReverseMap();
  8. CreateMap<UpdateBookDto, Book>()
  9. .ForMember(dest => dest.CreatedDate, opt => opt.Ignore())
  10. .ReverseMap();
  11. }
  12. }

添加CRUD服务

在Services目录下,创建IBookService接口:

  1. using Net6.with.MongoDB.Demo.Models;
  2. namespace Net6.with.MongoDB.Demo.Services;
  3. public interface IBookService
  4. {
  5. IList<Book> Get();
  6. Task<IList<Book>> GetAsync();
  7. Book Get(string id);
  8. Task<Book> GetAsync(string id);
  9. Book Create(Book book);
  10. Task<Book> CreateAsync(Book book);
  11. void Update(string id, Book bookIn);
  12. Task UpdateAsync(string id, Book bookIn);
  13. void Remove(string id);
  14. Task RemoveAsync(string id);
  15. }

然后,创建BookService 实现 IBookService 接口。

  1. using Microsoft.Extensions.Options;
  2. using MongoDB.Driver;
  3. using Net6.with.MongoDB.Demo.Models;
  4. namespace Net6.with.MongoDB.Demo.Services;
  5. public class BookService : IBookService
  6. {
  7. private readonly IMongoCollection<Book> _books;
  8. public BookService(IAppSetting settings)
  9. {
  10. var mongoClient = new MongoClient(settings.MongoDbSetting.Connection);
  11. var mongoDatabase = mongoClient.GetDatabase(settings.MongoDbSetting.Database);
  12. _books = mongoDatabase.GetCollection<Book>(settings.MongoDbSetting.CollectionName);
  13. }
  14. //错误
  15. //public BookService(IOptionsSnapshot<AppSetting> settings)
  16. //{
  17. // var mongoClient = new MongoClient(settings.Value.MongoDbSetting.Connection);
  18. // var mongoDatabase = mongoClient.GetDatabase(settings.Value.MongoDbSetting.Database);
  19. // _books = mongoDatabase.GetCollection<Book>(settings.Value.MongoDbSetting.CollectionName);
  20. //}
  21. public Book Create(Book book)
  22. {
  23. _books.InsertOne(book);
  24. return book;
  25. }
  26. public async Task<Book> CreateAsync(Book book)
  27. {
  28. await _books.InsertOneAsync(book);
  29. return book;
  30. }
  31. public IList<Book> Get()
  32. {
  33. return _books.Find(book => true).ToList();
  34. }
  35. public async Task<IList<Book>> GetAsync()
  36. {
  37. return await _books.Find(book => true).ToListAsync();
  38. }
  39. public Book Get(string id)
  40. {
  41. return _books.Find(book => book.Id == id).FirstOrDefault();
  42. }
  43. public async Task<Book> GetAsync(string id)
  44. {
  45. return await _books.Find(book => book.Id == id).FirstOrDefaultAsync();
  46. }
  47. public void Remove(string id)
  48. {
  49. _books.DeleteOne(book => book.Id == id);
  50. }
  51. public async Task RemoveAsync(string id)
  52. {
  53. await _books.DeleteOneAsync(book => book.Id == id);
  54. }
  55. public void Update(string id, Book bookIn)
  56. {
  57. _books.ReplaceOne(book => book.Id == id, bookIn);
  58. }
  59. public async Task UpdateAsync(string id, Book bookIn)
  60. {
  61. await _books.ReplaceOneAsync(book => book.Id == id, bookIn);
  62. }
  63. }

在上面的代码中,会通过构造函数从DI检索IBookStoreDatabaseSettings实例获取MongoDB连接字符串、数据库名 和 集合名。
当然,我们也可以使用 约定大于配置 的方式,统一采用实体类的名字 作为默认的 集合名,示例如下:

  1. _books = mongoDatabase.GetCollection<Book>(typeof(Book).Name);

最后,将BookService也加入到IoC容器中:

  1. services.AddSingleton<IBookService, BookService>();

这里,将BookService作为单一实例注入,这是因为 BookService 直接依赖于 MongoClient,而根据官方Mongo Client重用准则,我们应该使用单一实例服务在IoC容器中注入MongoClient。

添加Controller

在Controllers目录下,新增 BookController 控制器:

  1. using AutoMapper;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Net6.with.MongoDB.Demo.Models;
  4. using Net6.with.MongoDB.Demo.Services;
  5. namespace Net6.with.MongoDB.Demo.Controllers.MQ;
  6. /// <summary>
  7. ///
  8. /// </summary>
  9. [ApiController]
  10. public class BookController : AreaController
  11. {
  12. private readonly IMapper _mapper;
  13. private readonly IBookService _bookService;
  14. public BookController(IMapper mapper, IBookService bookService)
  15. {
  16. _mapper = mapper;
  17. _bookService = bookService;
  18. }
  19. [HttpGet]
  20. [ProducesResponseType(typeof(IList<Book>), StatusCodes.Status200OK)]
  21. [ProducesResponseType(typeof(IList<Book>), StatusCodes.Status204NoContent)]
  22. public async Task<ActionResult<IList<Book>>> Get()
  23. {
  24. var books = await _bookService.GetAsync();
  25. if (books == null)
  26. {
  27. return NoContent();
  28. }
  29. return Ok(books);
  30. }
  31. [HttpGet("{id:length(24)}", Name = "GetBook")]
  32. [ProducesResponseType(typeof(Book), StatusCodes.Status200OK)]
  33. [ProducesResponseType(typeof(Book), StatusCodes.Status404NotFound)]
  34. public async Task<ActionResult<Book>> Get(string id)
  35. {
  36. var book = await _bookService.GetAsync(id);
  37. if (book == null)
  38. {
  39. return NotFound();
  40. }
  41. return Ok(book);
  42. }
  43. [HttpPost]
  44. [ProducesResponseType(typeof(Book), StatusCodes.Status201Created)]
  45. public async Task<ActionResult<Book>> Create(CreateBookDto bookDto)
  46. {
  47. var book = _mapper.Map<Book>(bookDto);
  48. await _bookService.CreateAsync(book);
  49. return CreatedAtRoute("GetBook", new { id = book.Id.ToString() }, bookDto);
  50. }
  51. [HttpPut("{id:length(24)}")]
  52. [ProducesResponseType(StatusCodes.Status200OK)]
  53. [ProducesResponseType(StatusCodes.Status404NotFound)]
  54. public async Task<ActionResult> Update(string id, UpdateBookDto bookDto)
  55. {
  56. var book = await _bookService.GetAsync(id);
  57. if (book == null)
  58. {
  59. return NotFound();
  60. }
  61. _mapper.Map(bookDto, book);
  62. await _bookService.UpdateAsync(id, book);
  63. return Ok();
  64. }
  65. [HttpDelete("{id:length(24)}")]
  66. [ProducesResponseType(StatusCodes.Status200OK)]
  67. [ProducesResponseType(StatusCodes.Status404NotFound)]
  68. public async Task<ActionResult> Update(string id)
  69. {
  70. var book = await _bookService.GetAsync(id);
  71. if (book == null)
  72. {
  73. return NotFound();
  74. }
  75. await _bookService.RemoveAsync(id);
  76. return Ok();
  77. }
  78. }

测试WebAPI

生成该ASP.NET Core WebAPI应用,启动之后在Swagger页面进行测试:

参考

MongoDB入门实战教程(5)
Microsoft Doc:使用ASP.NET Core和MongoDB创建WebAPI