ASP.NET Core MongoDB:NoSQL数据库集成实战指南
引言:为什么选择MongoDB与ASP.NET Core?
在现代Web应用开发中,数据存储方案的选择至关重要。传统的关系型数据库虽然成熟稳定,但在处理非结构化数据、高并发读写和海量数据存储方面存在局限性。MongoDB作为领先的NoSQL(非关系型数据库)解决方案,与ASP.NET Core的结合为开发者提供了强大的数据管理能力。
读完本文你将掌握:
- MongoDB核心概念与ASP.NET Core集成原理
- 官方MongoDB.Driver驱动程序的完整配置流程
- 基于Repository模式的CRUD操作最佳实践
- 高级查询、聚合管道和事务处理技巧
- 性能优化与生产环境部署策略
一、环境准备与项目配置
1.1 安装必要依赖
首先通过NuGet包管理器安装MongoDB官方驱动程序:
<PackageReference Include="MongoDB.Driver" Version="2.19.0" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.19.0" />
<PackageReference Include="MongoDB.Bson" Version="2.19.0" />
1.2 配置MongoDB连接字符串
在appsettings.json中配置MongoDB连接信息:
{
"MongoDBSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "AspNetCoreMongoDemo",
"CollectionName": "Products"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
1.3 依赖注入配置
在Program.cs中注册MongoDB服务:
using MongoDB.Driver;
using Microsoft.Extensions.Options;
var builder = WebApplication.CreateBuilder(args);
// 配置MongoDB设置
builder.Services.Configure<MongoDBSettings>(
builder.Configuration.GetSection(nameof(MongoDBSettings)));
// 注册MongoDB客户端单例
builder.Services.AddSingleton<IMongoClient>(serviceProvider =>
{
var settings = serviceProvider.GetRequiredService<IOptions<MongoDBSettings>>().Value;
return new MongoClient(settings.ConnectionString);
});
// 注册数据库实例
builder.Services.AddScoped(serviceProvider =>
{
var settings = serviceProvider.GetRequiredService<IOptions<MongoDBSettings>>().Value;
var client = serviceProvider.GetRequiredService<IMongoClient>();
return client.GetDatabase(settings.DatabaseName);
});
// 添加控制器服务
builder.Services.AddControllers();
二、数据模型设计
2.1 定义实体类
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
[BsonElement("name")]
public required string Name { get; set; }
[BsonElement("description")]
public string? Description { get; set; }
[BsonElement("price")]
public decimal Price { get; set; }
[BsonElement("category")]
public required string Category { get; set; }
[BsonElement("tags")]
public List<string> Tags { get; set; } = new();
[BsonElement("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[BsonElement("updatedAt")]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
// 配置类
public class MongoDBSettings
{
public required string ConnectionString { get; set; }
public required string DatabaseName { get; set; }
public required string CollectionName { get; set; }
}
2.2 实体关系映射示意图
三、Repository模式实现
3.1 定义通用Repository接口
public interface IRepository<T> where T : class
{
Task<IEnumerable<T>> GetAllAsync();
Task<T?> GetByIdAsync(string id);
Task CreateAsync(T entity);
Task UpdateAsync(string id, T entity);
Task DeleteAsync(string id);
Task<bool> ExistsAsync(string id);
}
3.2 MongoDB Repository实现
using MongoDB.Driver;
public class MongoRepository<T> : IRepository<T> where T : class
{
private readonly IMongoCollection<T> _collection;
public MongoRepository(IMongoDatabase database, string collectionName)
{
_collection = database.GetCollection<T>(collectionName);
}
public async Task<IEnumerable<T>> GetAllAsync()
{
return await _collection.Find(_ => true).ToListAsync();
}
public async Task<T?> GetByIdAsync(string id)
{
var filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
return await _collection.Find(filter).FirstOrDefaultAsync();
}
public async Task CreateAsync(T entity)
{
await _collection.InsertOneAsync(entity);
}
public async Task UpdateAsync(string id, T entity)
{
var filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
await _collection.ReplaceOneAsync(filter, entity);
}
public async Task DeleteAsync(string id)
{
var filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
await _collection.DeleteOneAsync(filter);
}
public async Task<bool> ExistsAsync(string id)
{
var filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
return await _collection.CountDocumentsAsync(filter) > 0;
}
}
3.3 注册Repository服务
// 在Program.cs中添加
builder.Services.AddScoped<IRepository<Product>>(serviceProvider =>
{
var database = serviceProvider.GetRequiredService<IMongoDatabase>();
var settings = serviceProvider.GetRequiredService<IOptions<MongoDBSettings>>().Value;
return new MongoRepository<Product>(database, settings.CollectionName);
});
四、控制器实现与API设计
4.1 Products控制器
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IRepository<Product> _productRepository;
public ProductsController(IRepository<Product> productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
var products = await _productRepository.GetAllAsync();
return Ok(products);
}
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(string id)
{
var product = await _productRepository.GetByIdAsync(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
[HttpPost]
public async Task<ActionResult<Product>> CreateProduct(Product product)
{
await _productRepository.CreateAsync(product);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(string id, Product product)
{
if (!await _productRepository.ExistsAsync(id))
{
return NotFound();
}
product.Id = id;
await _productRepository.UpdateAsync(id, product);
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(string id)
{
if (!await _productRepository.ExistsAsync(id))
{
return NotFound();
}
await _productRepository.DeleteAsync(id);
return NoContent();
}
}
4.2 API端点功能表
| HTTP方法 | 端点 | 功能描述 | 状态码 |
|---|---|---|---|
| GET | /api/products | 获取所有产品 | 200 OK |
| GET | /api/products/{id} | 获取指定产品 | 200 OK / 404 Not Found |
| POST | /api/products | 创建新产品 | 201 Created |
| PUT | /api/products/{id} | 更新产品信息 | 204 No Content / 404 Not Found |
| DELETE | /api/products/{id} | 删除产品 | 204 No Content / 404 Not Found |
五、高级查询与聚合操作
5.1 扩展Repository支持复杂查询
public interface IProductRepository : IRepository<Product>
{
Task<IEnumerable<Product>> GetByCategoryAsync(string category);
Task<IEnumerable<Product>> SearchByNameAsync(string name);
Task<IEnumerable<Product>> GetProductsInPriceRangeAsync(decimal minPrice, decimal maxPrice);
Task<long> GetTotalCountAsync();
}
public class ProductRepository : MongoRepository<Product>, IProductRepository
{
public ProductRepository(IMongoDatabase database, string collectionName)
: base(database, collectionName)
{
}
public async Task<IEnumerable<Product>> GetByCategoryAsync(string category)
{
var filter = Builders<Product>.Filter.Eq(p => p.Category, category);
return await _collection.Find(filter).ToListAsync();
}
public async Task<IEnumerable<Product>> SearchByNameAsync(string name)
{
var filter = Builders<Product>.Filter.Regex(p => p.Name,
new BsonRegularExpression(name, "i")); // 不区分大小写
return await _collection.Find(filter).ToListAsync();
}
public async Task<IEnumerable<Product>> GetProductsInPriceRangeAsync(decimal minPrice, decimal maxPrice)
{
var filter = Builders<Product>.Filter.And(
Builders<Product>.Filter.Gte(p => p.Price, minPrice),
Builders<Product>.Filter.Lte(p => p.Price, maxPrice)
);
return await _collection.Find(filter).ToListAsync();
}
public async Task<long> GetTotalCountAsync()
{
return await _collection.CountDocumentsAsync(_ => true);
}
}
5.2 聚合管道示例
public async Task<Dictionary<string, int>> GetProductCountByCategoryAsync()
{
var pipeline = new BsonDocument[]
{
new BsonDocument("$group",
new BsonDocument
{
{ "_id", "$category" },
{ "count", new BsonDocument("$sum", 1) }
}
),
new BsonDocument("$sort", new BsonDocument("count", -1))
};
var results = await _collection.Aggregate<BsonDocument>(pipeline).ToListAsync();
return results.ToDictionary(
x => x["_id"].AsString,
x => x["count"].AsInt32
);
}
六、性能优化策略
6.1 索引优化
// 在Repository构造函数中创建索引
public ProductRepository(IMongoDatabase database, string collectionName)
: base(database, collectionName)
{
// 创建复合索引
var indexKeys = Builders<Product>.IndexKeys
.Ascending(p => p.Category)
.Ascending(p => p.Price);
var indexOptions = new CreateIndexOptions { Name = "Category_Price_Index" };
var indexModel = new CreateIndexModel<Product>(indexKeys, indexOptions);
_collection.Indexes.CreateOne(indexModel);
// 创建文本搜索索引
var textIndexKeys = Builders<Product>.IndexKeys.Text(p => p.Name);
var textIndexModel = new CreateIndexModel<Product>(textIndexKeys);
_collection.Indexes.CreateOne(textIndexModel);
}
6.2 查询性能优化表
| 优化策略 | 实施方法 | 性能提升效果 |
|---|---|---|
| 索引优化 | 为常用查询字段创建索引 | 查询速度提升10-100倍 |
| 投影优化 | 只返回需要的字段 | 减少网络传输和数据处理时间 |
| 分页查询 | 使用Skip和Limit | 减少内存使用和响应时间 |
| 批量操作 | 使用BulkWrite | 减少网络往返次数 |
| 连接池 | 合理配置连接池大小 | 提高并发处理能力 |
七、事务处理与数据一致性
7.1 多文档事务支持
public async Task<bool> TransferProductCategory(string productId, string newCategory)
{
using var session = await _client.StartSessionAsync();
session.StartTransaction();
try
{
var product = await _collection.Find(session, p => p.Id == productId).FirstOrDefaultAsync();
if (product == null)
return false;
product.Category = newCategory;
product.UpdatedAt = DateTime.UtcNow;
await _collection.ReplaceOneAsync(session, p => p.Id == productId, product);
// 可以在这里添加其他相关操作
await session.CommitTransactionAsync();
return true;
}
catch
{
await session.AbortTransactionAsync();
throw;
}
}
7.2 数据一致性保障机制
八、测试策略
8.1 单元测试示例
using Moq;
using Xunit;
public class ProductRepositoryTests
{
private readonly Mock<IMongoCollection<Product>> _mockCollection;
private readonly ProductRepository _repository;
public ProductRepositoryTests()
{
_mockCollection = new Mock<IMongoCollection<Product>>();
var mockDatabase = new Mock<IMongoDatabase>();
mockDatabase.Setup(d => d.GetCollection<Product>("Products", null))
.Returns(_mockCollection.Object);
_repository = new ProductRepository(mockDatabase.Object, "Products");
}
[Fact]
public async Task CreateAsync_ShouldInsertProduct()
{
// Arrange
var product = new Product { Name = "Test Product", Price = 100, Category = "Test" };
// Act
await _repository.CreateAsync(product);
// Assert
_mockCollection.Verify(c => c.InsertOneAsync(
product,
null,
It.IsAny<CancellationToken>()),
Times.Once);
}
[Fact]
public async Task GetByIdAsync_WithValidId_ReturnsProduct()
{
// Arrange
var productId = ObjectId.GenerateNewId().ToString();
var expectedProduct = new Product { Id = productId, Name = "Test Product" };
var mockCursor = new Mock<IAsyncCursor<Product>>();
mockCursor.SetupSequence(_ => _.MoveNextAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(true)
.ReturnsAsync(false);
mockCursor.Setup(_ => _.Current).Returns(new[] { expectedProduct });
_mockCollection.Setup(c => c.FindAsync(
It.IsAny<FilterDefinition<Product>>(),
It.IsAny<FindOptions<Product, Product>>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(mockCursor.Object);
// Act
var result = await _repository.GetByIdAsync(productId);
// Assert
Assert.NotNull(result);
Assert.Equal(productId, result.Id);
}
}
九、生产环境部署建议
9.1 连接字符串配置最佳实践
{
"MongoDBSettings": {
"ConnectionString": "mongodb://username:password@cluster0-shard-00-00.mongodb.net:27017,cluster0-shard-00-01.mongodb.net:27017,cluster0-shard-00-02.mongodb.net:27017/myDatabase?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true&w=majority",
"DatabaseName": "production_db",
"CollectionName": "products"
}
}
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



