MongoDB 集成
本文档解释了如何将 MongoDB 作为数据库提供程序集成到基于 ABP 的应用程序中,以及如何进行配置。
安装
Volo.Abp.MongoDB 是用于 MongoDB 集成的主要 NuGet 包。将其安装到您的项目(对于分层应用程序,安装到您的数据/基础设施层)中。您可以使用 ABP CLI 将其安装到您的项目中。在层的 .csproj 文件所在文件夹中执行以下命令:
abp add-package Volo.Abp.MongoDB
然后将 AbpMongoDbModule 模块依赖项添加到您的 模块 中:
using Volo.Abp.MongoDB;
using Volo.Abp.Modularity;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpMongoDbModule))]
public class MyModule : AbpModule
{
//...
}
}
创建 Mongo Db 上下文
ABP 引入了 Mongo Db 上下文 概念(类似于 Entity Framework Core 的 DbContext),以便更轻松地使用和配置集合。示例如下:
public class MyDbContext : AbpMongoDbContext
{
public IMongoCollection<Question> Questions => Collection<Question>();
public IMongoCollection<Category> Categories => Collection<Category>();
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
//为您的集合自定义配置。
}
}
- 它派生自
AbpMongoDbContext类。 - 为每个 MongoDB 集合添加一个公共的
IMongoCollection<TEntity>属性。默认情况下,ABP 使用这些属性来创建默认仓库。 - 重写
CreateModel方法可以配置集合。
为集合配置映射
ABP 会自动为您的 DbContext 中所有的 IMongoCollection<TEntity> 属性将实体注册到 MongoDB 客户端库。对于上面的示例,Question 和 Category 实体会被自动注册。
对于每个注册的实体,它会调用 AutoMap() 并配置您实体的已知属性。例如,如果您的实体实现了 IHasExtraProperties 接口(默认情况下每个聚合根都已实现),它会自动配置 ExtraProperties。
因此,大多数时候您不需要显式配置实体的注册。但是,如果需要,您可以在 DbContext 中通过重写 CreateModel 方法来实现。示例:
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
modelBuilder.Entity<Question>(b =>
{
b.CollectionName = "MyQuestions"; //设置集合名称
b.BsonMap.UnmapProperty(x => x.MyProperty); //忽略 'MyProperty'
});
}
此示例将数据库中映射的集合名称更改为 'MyQuestions',并忽略 Question 类中的一个属性。
如果您只需要配置集合名称,也可以在您的 DbContext 中为集合使用 [MongoCollection] 属性。示例:
[MongoCollection("MyQuestions")] //设置集合名称
public IMongoCollection<Question> Questions => Collection<Question>();
为集合配置索引和 CreateCollectionOptions
您可以通过重写 CreateModel 方法来为您的集合配置索引和 CreateCollectionOptions。示例:
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
modelBuilder.Entity<Question>(b =>
{
b.CreateCollectionOptions.Collation = new Collation(locale:"en_US", strength: CollationStrength.Secondary);
b.ConfigureIndexes(indexes =>
{
indexes.CreateOne(
new CreateIndexModel<BsonDocument>(
Builders<BsonDocument>.IndexKeys.Ascending("MyProperty"),
new CreateIndexOptions { Unique = true }
)
);
}
);
});
}
此示例为集合设置了排序规则,并为 MyProperty 属性创建了一个唯一索引。
配置连接字符串选择
如果您的应用程序中有多个数据库,可以使用 [ConnectionStringName] 属性为您的 DbContext 配置连接字符串名称。示例:
[ConnectionStringName("MySecondConnString")]
public class MyDbContext : AbpMongoDbContext
{
}
如果不配置,则使用 Default 连接字符串。如果配置了特定的连接字符串名称,但在应用程序配置中未定义此连接字符串名称,则会回退到 Default 连接字符串。
将 DbContext 注册到依赖注入
在您的模块中使用 AddAbpDbContext 方法,将您的 DbContext 类注册到 依赖注入 系统。
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.MongoDB;
using Volo.Abp.Modularity;
namespace MyCompany.MyProject
{
[DependsOn(typeof(AbpMongoDbModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMongoDbContext<MyDbContext>();
//...
}
}
}
添加默认仓库
ABP 可以自动为您的 DbContext 中的实体创建默认的 通用仓库。只需在注册时使用 AddDefaultRepositories() 选项:
services.AddMongoDbContext<MyDbContext>(options =>
{
options.AddDefaultRepositories();
});
默认情况下,这将为每个 聚合根实体(派生自 AggregateRoot 的类)创建一个仓库。如果您也想为其他实体创建仓库,则将 includeAllEntities 设置为 true:
services.AddMongoDbContext<MyDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
然后,您可以在服务中注入和使用 IRepository<TEntity, TPrimaryKey>。假设您有一个主键为 Guid 的 Book 实体:
public class Book : AggregateRoot<Guid>
{
public string Name { get; set; }
public BookType Type { get; set; }
}
(此处 BookType 是一个简单的 enum)并且您想要在 领域服务 中创建一个新的 Book 实体:
public class BookManager : DomainService
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookManager(IRepository<Book, Guid> bookRepository) //注入默认仓库
{
_bookRepository = bookRepository;
}
public async Task<Book> CreateBook(string name, BookType type)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
var book = new Book
{
Id = GuidGenerator.Create(),
Name = name,
Type = type
};
await _bookRepository.InsertAsync(book); //使用标准仓库方法
return book;
}
}
此示例使用 InsertAsync 方法将新实体插入数据库。
添加自定义仓库
在大多数情况下,默认的通用仓库功能足够强大(因为它们实现了 IQueryable)。但是,您可能需要创建自定义仓库来添加自己的仓库方法。
假设您想按类型删除所有书籍。建议为您的自定义仓库定义一个接口:
public interface IBookRepository : IRepository<Book, Guid>
{
Task DeleteBooksByType(
BookType type,
CancellationToken cancellationToken = default(CancellationToken)
);
}
通常,您希望从 IRepository 派生以继承标准仓库方法。但这不是必须的。仓库接口定义在分层应用程序的领域层中。它们在数据/基础设施层(启动模板 中的 MongoDB 项目)中实现。
IBookRepository 接口的示例实现:
public class BookRepository :
MongoDbRepository<BookStoreMongoDbContext, Book, Guid>,
IBookRepository
{
public BookRepository(IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task DeleteBooksByType(
BookType type,
CancellationToken cancellationToken = default(CancellationToken))
{
var collection = await GetCollectionAsync(cancellationToken);
await collection.DeleteManyAsync(
Builders<Book>.Filter.Eq(b => b.Type, type),
cancellationToken
);
}
}
现在,可以在需要时 注入 IBookRepository 并使用 DeleteBooksByType 方法。
覆盖默认通用仓库
即使您创建了自定义仓库,您仍然可以注入默认的通用仓库(对于此示例是 IRepository<Book, Guid>)。默认的仓库实现将不使用您创建的类。
如果您想用您的自定义仓库替换默认的仓库实现,请在 AddMongoDbContext 选项中执行:
context.Services.AddMongoDbContext<BookStoreMongoDbContext>(options =>
{
options.AddDefaultRepositories();
options.AddRepository<Book, BookRepository>(); //替换 IRepository<Book, Guid>
});
当您想要 覆盖基本仓库方法 以自定义它时,这一点尤其重要。例如,您可能希望覆盖 DeleteAsync 方法,以更高效的方式删除实体:
public async override Task DeleteAsync(
Guid id,
bool autoSave = false,
CancellationToken cancellationToken = default)
{
//TODO: 删除方法的自定义实现
}
访问 MongoDB API
在大多数情况下,您希望将 MongoDB API 隐藏在仓库后面(这是仓库的主要目的)。但是,如果您想通过仓库访问 MongoDB API,可以使用 GetDatabaseAsync()、GetCollectionAsync() 或 GetAggregateAsync() 扩展方法。示例:
public class BookService
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookService(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
public async Task FooAsync()
{
IMongoDatabase database = await _bookRepository.GetDatabaseAsync();
IMongoCollection<Book> books = await _bookRepository.GetCollectionAsync();
IAggregateFluent<Book> bookAggregate = await _bookRepository.GetAggregateAsync();
}
}
重要:您必须从要访问 MongoDB API 的项目中引用
Volo.Abp.MongoDB包。这破坏了封装性,但这是您在这种情况下所期望的。
事务
MongoDB 从 4.0 版本开始支持多文档事务,ABP 也支持它。但是,启动模板 默认禁用 事务。如果您的 MongoDB 服务器 支持事务,您可以在 YourProjectMongoDbModule 类中启用它们:
删除以下代码以启用事务:
- context.Services.AddAlwaysDisableUnitOfWorkTransaction();
- Configure<AbpUnitOfWorkDefaultOptions>(options =>
- {
- options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
- });
在 Docker 中设置支持事务的 MongoDB 副本集
使用以下 docker-compose.yml 创建一个支持事务的本地 MongoDB 副本集。连接字符串将为 mongodb://localhost:27017/YourProjectName?replicaSet=rs0。
version: "3.8"
services:
mongo:
image: mongo:8.0
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports:
- 27017:27017
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]}) }" | mongosh --port 27017 --quiet
interval: 5s
timeout: 30s
start_period: 0s
start_interval: 1s
retries: 30
高级主题
控制多租户
如果您的解决方案是 多租户 的,租户可能有 独立的数据库,您的解决方案中有 多个 DbContext 类,并且您的某些 DbContext 类应该 只能从宿主端使用,建议在您的 DbContext 类上添加 [IgnoreMultiTenancy] 属性。在这种情况下,ABP 保证相关的 DbContext 始终使用宿主 连接字符串,即使您在租户上下文中。
示例:
[IgnoreMultiTenancy]
public class MyDbContext : AbpMongoDbContext
{
...
}
如果您的 DbContext 中的任何实体可以保存在租户数据库中,请不要使用 [IgnoreMultiTenancy] 属性。
当您使用仓库时,ABP 已经对未实现
IMultiTenant接口的实体使用宿主数据库。因此,在大多数情况下,如果您使用仓库来处理数据库,则不需要[IgnoreMultiTenancy]属性。
设置默认仓库类
默认的通用仓库默认由 MongoDbRepository 类实现。您可以创建自己的实现并将其用于默认仓库实现。
首先,像这样定义您的仓库类:
public class MyRepositoryBase<TEntity>
: MongoDbRepository<BookStoreMongoDbContext, TEntity>
where TEntity : class, IEntity
{
public MyRepositoryBase(IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
public class MyRepositoryBase<TEntity, TKey>
: MongoDbRepository<BookStoreMongoDbContext, TEntity, TKey>
where TEntity : class, IEntity<TKey>
{
public MyRepositoryBase(IMongoDbContextProvider<BookStoreMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
第一个用于 具有复合键的实体,第二个用于具有单主键的实体。
建议从 MongoDbRepository 类继承并在需要时覆盖方法。否则,您必须手动实现所有标准仓库方法。
现在,您可以使用 SetDefaultRepositoryClasses 选项:
context.Services.AddMongoDbContext<BookStoreMongoDbContext>(options =>
{
options.SetDefaultRepositoryClasses(
typeof(MyRepositoryBase<,>),
typeof(MyRepositoryBase<>)
);
//...
});
为默认仓库设置基础 MongoDbContext 类或接口
如果您的 MongoDbContext 继承自另一个 MongoDbContext 或实现了一个接口,您可以将该基类或接口用作默认仓库的 MongoDbContext。示例:
public interface IBookStoreMongoDbContext : IAbpMongoDbContext
{
Collection<Book> Books { get; }
}
IBookStoreMongoDbContext 由 BookStoreMongoDbContext 类实现。然后,您可以使用 AddDefaultRepositories 的泛型重载:
context.Services.AddMongoDbContext<BookStoreMongoDbContext>(options =>
{
options.AddDefaultRepositories<IBookStoreMongoDbContext>();
//...
});
现在,您的自定义 BookRepository 也可以使用 IBookStoreMongoDbContext 接口:
public class BookRepository
: MongoDbRepository<IBookStoreMongoDbContext, Book, Guid>,
IBookRepository
{
//...
}
为 MongoDbContext 使用接口的一个优点是它变得可以被另一个实现替换。
替换其他 DbContext
一旦您为 MongoDbContext 正确定义并使用了接口,那么任何其他实现都可以使用以下方式替换它:
ReplaceDbContext 属性
[ReplaceDbContext(typeof(IBookStoreMongoDbContext))]
public class OtherMongoDbContext : AbpMongoDbContext, IBookStoreMongoDbContext
{
//...
}
ReplaceDbContext 选项
context.Services.AddMongoDbContext<OtherMongoDbContext>(options =>
{
//...
options.ReplaceDbContext<IBookStoreMongoDbContext>();
});
在此示例中,OtherMongoDbContext 实现了 IBookStoreMongoDbContext。此功能允许您在开发时拥有多个 MongoDbContext(每个模块一个),但在运行时拥有单个 MongoDbContext(实现所有 MongoDbContext 的所有接口)。
多租户替换
还可以基于 多租户 方来替换 DbContext。ReplaceDbContext 属性和 ReplaceDbContext 方法可以获取一个 MultiTenancySides 选项,默认值为 MultiTenancySides.Both。
示例: 仅针对租户替换 DbContext,使用 ReplaceDbContext 属性
[ReplaceDbContext(typeof(IBookStoreDbContext), MultiTenancySides.Tenant)]
示例: 仅针对宿主方替换 DbContext,使用 ReplaceDbContext 方法
options.ReplaceDbContext<IBookStoreDbContext>(MultiTenancySides.Host);
自定义批量操作
如果您有更好的逻辑或使用外部库进行批量操作,可以通过实现 IMongoDbBulkOperationProvider 来覆盖该逻辑。
- 您可以使用以下示例模板:
public class MyCustomMongoDbBulkOperationProvider
: IMongoDbBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// 在此处编写您的逻辑。
}
public async Task InsertManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// 在此处编写您的逻辑。
}
public async Task UpdateManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// 在此处编写您的逻辑。
}
}
抠丁客


