3.1 实体与数据库设计
3.1.1 实体设计基础概念
实体是领域驱动设计(DDD)中的核心概念,代表业务领域中的具体事物,具有唯一标识和状态。在ABP框架中,实体是数据库表的映射对象,用于表示业务数据和行为。
实体设计的核心原则包括:
- 唯一标识:每个实体必须有一个唯一标识符
- 封装:实体应封装业务逻辑,保持数据完整性
- 不变性:实体的核心属性在创建后不应轻易修改
- 关系:实体之间的关系应清晰定义,包括一对一、一对多和多对多关系
- 领域语义:实体名称和属性应反映业务领域的语义
3.1.2 ABP框架中的实体基类
ABP框架提供了一系列实体基类,简化了实体的创建和管理:
| Entity<TKey> | 基本实体基类,包含ID属性 | 所有需要唯一标识的实体 |
| AggregateRoot<TKey> | 聚合根基类,继承自Entity | 作为聚合根的实体 |
| CreationAuditedEntity<TKey> | 包含创建审计信息的实体 | 需要记录创建时间和创建者的实体 |
| AuditedEntity<TKey> | 包含完整审计信息的实体 | 需要记录创建、修改和删除信息的实体 |
| FullAuditedEntity<TKey> | 包含完整审计信息和软删除的实体 | 需要软删除功能的实体 |
3.1.3 实体类创建
3.1.3.1 基本实体创建
创建一个基本的图书实体:
using System;
using Volo.Abp.Domain.Entities;
namespace BookStore.Domain.Books
{
// 基本实体,继承自Entity<Guid>
public class Book : Entity<Guid>
{
// 属性
public string Name { get; private set; }
public string Isbn { get; private set; }
public decimal Price { get; private set; }
public Guid AuthorId { get; private set; }
public bool IsBorrowed { get; private set; }
// 导航属性
public Author Author { get; private set; }
// 受保护的无参构造函数,EF Core需要
protected Book() { }
// 公共构造函数,用于创建新实体
public Book(string name, string isbn, decimal price, Guid authorId)
{
Name = name;
Isbn = isbn;
Price = price;
AuthorId = authorId;
IsBorrowed = false;
}
// 业务方法
public void MarkAsBorrowed()
{
if (IsBorrowed)
{
throw new BusinessException("BookAlreadyBorrowed");
}
IsBorrowed = true;
}
public void MarkAsReturned()
{
if (!IsBorrowed)
{
throw new BusinessException("BookNotBorrowed");
}
IsBorrowed = false;
}
public void UpdatePrice(decimal newPrice)
{
if (newPrice <= 0)
{
throw new BusinessException("PriceMustBePositive");
}
Price = newPrice;
}
}
}
3.1.3.2 审计实体创建
创建一个包含审计信息的作者实体:
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
namespace BookStore.Domain.Authors
{
// 包含完整审计信息的实体
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public string Email { get; private set; }
public DateTime BirthDate { get; private set; }
// 导航属性
public List<Book> Books { get; private set; } = new List<Book>();
protected Author() { }
public Author(string name, string email, DateTime birthDate)
{
Name = name;
Email = email;
BirthDate = birthDate;
}
public void UpdateName(string newName)
{
if (string.IsNullOrWhiteSpace(newName))
{
throw new BusinessException("AuthorNameCannotBeEmpty");
}
Name = newName;
}
public void UpdateEmail(string newEmail)
{
if (string.IsNullOrWhiteSpace(newEmail) || !newEmail.Contains("@"))
{
throw new BusinessException("InvalidEmailFormat");
}
Email = newEmail;
}
}
}
3.1.4 数据库映射配置
ABP框架使用Entity Framework Core进行数据库访问,支持多种数据库映射方式:
3.1.4.1 使用数据注解
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Volo.Abp.Domain.Entities.Auditing;
namespace BookStore.Domain.Books
{
public class Book : AuditedEntity<Guid>
{
[Required(ErrorMessage = "BookNameIsRequired")]
[StringLength(100, ErrorMessage = "BookNameLengthExceeded")]
public string Name { get; private set; }
[Required(ErrorMessage = "IsbnIsRequired")]
[StringLength(20, ErrorMessage = "IsbnLengthExceeded")]
[Index(IsUnique = true, Name = "IX_Books_Isbn")]
public string Isbn { get; private set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; private set; }
// 其他属性和方法…
}
}
3.1.4.2 使用Fluent API
创建DbContext类并使用Fluent API进行映射配置:
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using BookStore.Domain.Books;
using BookStore.Domain.Authors;
using BookStore.Domain.Borrowing;
namespace BookStore.EntityFrameworkCore
{
[ConnectionStringName("Default")]
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
// DbSet属性
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<BorrowingRecord> BorrowingRecords { get; set; }
public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options)
: base(options)
{
}
// 配置实体映射
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 配置图书实体
builder.Entity<Book>(b =>
{
// 表名
b.ToTable("Books");
// 主键
b.HasKey(x => x.Id);
// 属性配置
b.Property(x => x.Name)
.IsRequired()
.HasMaxLength(100);
b.Property(x => x.Isbn)
.IsRequired()
.HasMaxLength(20);
b.Property(x => x.Price)
.HasColumnType("decimal(18,2)");
// 索引
b.HasIndex(x => x.Isbn).IsUnique();
b.HasIndex(x => x.AuthorId);
// 关系配置
b.HasOne(x => x.Author)
.WithMany(x => x.Books)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.Cascade);
});
// 配置作者实体
builder.Entity<Author>(b =>
{
b.ToTable("Authors");
b.HasKey(x => x.Id);
b.Property(x => x.Name)
.IsRequired()
.HasMaxLength(100);
b.Property(x => x.Email)
.IsRequired()
.HasMaxLength(100);
b.HasIndex(x => x.Email).IsUnique();
});
// 配置借阅记录实体
builder.Entity<BorrowingRecord>(b =>
{
b.ToTable("BorrowingRecords");
b.HasKey(x => x.Id);
// 关系配置
b.HasOne(x => x.Book)
.WithMany()
.HasForeignKey(x => x.BookId)
.OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.User)
.WithMany()
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Restrict);
});
}
}
}
3.1.4.3 PostgreSQL支持配置
ABP框架支持多种数据库,包括PostgreSQL。要在ABP项目中使用PostgreSQL,需要进行以下配置:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Volo.Abp.EntityFrameworkCore.PostgreSql
在appsettings.json文件中添加PostgreSQL连接字符串:
{
"ConnectionStrings": {
"Default": "Host=localhost;Database=BookStore;Username=postgres;Password=your_password"
}
}
在模块类中配置PostgreSQL数据库提供程序:
[DependsOn(
typeof(BookStoreDomainModule),
typeof(AbpEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCorePostgreSqlModule) // 依赖PostgreSQL模块
)]
public class BookStoreEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 注册DbContext
context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
{
options.AddDefaultRepositories();
});
// 配置EF Core使用PostgreSQL
Configure<AbpDbContextOptions>(options =>
{
options.Configure<BookStoreDbContext>(dbContextOptions =>
{
dbContextOptions.UseNpgsql();
});
});
}
}
3.1.4.4 PostgreSQL特定数据类型
PostgreSQL支持多种高级数据类型,可以在ABP项目中充分利用:
// 使用PostgreSQL的UUID类型作为主键
public class Book : AuditedEntity<Guid>
{
public Guid Id { get; set; } // EF Core会自动映射到PostgreSQL的UUID类型
// 其他属性…
}
// 使用PostgreSQL的JSONB类型存储复杂数据
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用JSONB类型存储产品属性
[Column(TypeName = "jsonb")]
public string Attributes { get; set; }
// 使用JSONB类型存储产品规格
[Column(TypeName = "jsonb")]
public string Specifications { get; set; }
}
// 使用PostgreSQL的数组类型
public class Author
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用PostgreSQL的字符串数组类型
[Column(TypeName = "text[]")]
public string[] Tags { get; set; }
// 使用PostgreSQL的整数数组类型
[Column(TypeName = "integer[]")]
public int[] BookIds { get; set; }
}
// 使用PostgreSQL的HStore类型(键值对)
public class Setting
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用PostgreSQL的HStore类型存储设置值
[Column(TypeName = "hstore")]
public string Values { get; set; }
}
// 使用PostgreSQL的Range类型
public class Event
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用PostgreSQL的日期范围类型
[Column(TypeName = "daterange")]
public string Duration { get; set; }
// 使用PostgreSQL的整数范围类型
[Column(TypeName = "int4range")]
public string Capacity { get; set; }
}
3.1.4.2 使用数据注解
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Volo.Abp.Domain.Entities.Auditing;
namespace BookStore.Domain.Books
{
public class Book : AuditedEntity<Guid>
{
[Required(ErrorMessage = "BookNameIsRequired")]
[StringLength(100, ErrorMessage = "BookNameLengthExceeded")]
public string Name { get; private set; }
[Required(ErrorMessage = "IsbnIsRequired")]
[StringLength(20, ErrorMessage = "IsbnLengthExceeded")]
[Index(IsUnique = true, Name = "IX_Books_Isbn")]
public string Isbn { get; private set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; private set; }
// 其他属性和方法…
}
}
3.1.4.3 PostgreSQL支持配置
ABP框架支持多种数据库,包括PostgreSQL。要使用PostgreSQL,需要在项目中添加以下配置:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Volo.Abp.EntityFrameworkCore.PostgreSql
在appsettings.json文件中添加PostgreSQL连接字符串:
{
"ConnectionStrings": {
"Default": "Host=localhost;Database=BookStore;Username=postgres;Password=your_password"
}
}
在模块类中配置PostgreSQL数据库提供程序:
[DependsOn(
typeof(BookStoreDomainModule),
typeof(AbpEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCorePostgreSqlModule) // 依赖PostgreSQL模块
)]
public class BookStoreEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 注册DbContext
context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
{
options.AddDefaultRepositories();
});
// 配置EF Core使用PostgreSQL
Configure<AbpDbContextOptions>(options =>
{
options.Configure<BookStoreDbContext>(dbContextOptions =>
{
dbContextOptions.UseNpgsql();
});
});
}
}
3.1.4.4 PostgreSQL特定数据类型
PostgreSQL支持多种高级数据类型,可以在ABP框架中充分利用:
// 使用PostgreSQL的UUID类型作为主键
public class Book : AuditedEntity<Guid>
{
public Guid Id { get; set; } // EF Core会自动映射到PostgreSQL的UUID类型
// 其他属性…
}
// 使用PostgreSQL的JSONB类型存储复杂数据
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用JSONB类型存储产品属性
[Column(TypeName = "jsonb")]
public string Attributes { get; set; }
// 使用JSONB类型存储产品规格
[Column(TypeName = "jsonb")]
public string Specifications { get; set; }
}
// 使用PostgreSQL的数组类型
public class Author
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用PostgreSQL的字符串数组类型
[Column(TypeName = "text[]")]
public string[] Tags { get; set; }
// 使用PostgreSQL的整数数组类型
[Column(TypeName = "integer[]")]
public int[] BookIds { get; set; }
}
// 使用PostgreSQL的HStore类型(键值对)
public class Setting
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用PostgreSQL的HStore类型存储设置值
[Column(TypeName = "hstore")]
public string Values { get; set; }
}
// 使用PostgreSQL的Range类型
public class Event
{
public Guid Id { get; set; }
public string Name { get; set; }
// 使用PostgreSQL的日期范围类型
[Column(TypeName = "daterange")]
public string Duration { get; set; }
// 使用PostgreSQL的整数范围类型
[Column(TypeName = "int4range")]
public string Capacity { get; set; }
}
3.1.4.5 使用Fluent API
创建DbContext类并使用Fluent API进行映射配置:
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using BookStore.Domain.Books;
using BookStore.Domain.Authors;
using BookStore.Domain.Borrowing;
namespace BookStore.EntityFrameworkCore
{
[ConnectionStringName("Default")]
public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
// DbSet属性
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<BorrowingRecord> BorrowingRecords { get; set; }
public BookStoreDbContext(DbContextOptions<BookStoreDbContext> options)
: base(options)
{
}
// 配置实体映射
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 配置图书实体
builder.Entity<Book>(b =>
{
// 表名
b.ToTable("Books");
// 主键
b.HasKey(x => x.Id);
// 属性配置
b.Property(x => x.Name)
.IsRequired()
.HasMaxLength(100);
b.Property(x => x.Isbn)
.IsRequired()
.HasMaxLength(20);
b.Property(x => x.Price)
.HasColumnType("decimal(18,2)");
// 索引
b.HasIndex(x => x.Isbn).IsUnique();
b.HasIndex(x => x.AuthorId);
// 关系配置
b.HasOne(x => x.Author)
.WithMany(x => x.Books)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.Cascade);
});
// 配置作者实体
builder.Entity<Author>(b =>
{
b.ToTable("Authors");
b.HasKey(x => x.Id);
b.Property(x => x.Name)
.IsRequired()
.HasMaxLength(100);
b.Property(x => x.Email)
.IsRequired()
.HasMaxLength(100);
b.HasIndex(x => x.Email).IsUnique();
});
// 配置借阅记录实体
builder.Entity<BorrowingRecord>(b =>
{
b.ToTable("BorrowingRecords");
b.HasKey(x => x.Id);
// 关系配置
b.HasOne(x => x.Book)
.WithMany()
.HasForeignKey(x => x.BookId)
.OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.User)
.WithMany()
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Restrict);
});
}
}
}
3.1.5 迁移文件生成与执行
迁移文件是EF Core用于管理数据库模式变更的文件,包含数据库表的创建、修改和删除等操作。
3.1.5.1 生成迁移文件
使用EF Core CLI或Package Manager Console生成迁移文件:
# 使用EF Core CLI生成迁移文件
dotnet ef migrations add InitialCreate –project BookStore.EntityFrameworkCore –startup-project BookStore.Web
# 更新数据库
dotnet ef database update –project BookStore.EntityFrameworkCore –startup-project BookStore.Web
3.1.5.2 迁移文件结构
生成的迁移文件包含两个部分:
// 迁移类示例
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Email = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
BirthDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
CreationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
CreatorId = table.Column<Guid>(type: "uuid", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
LastModifierId = table.Column<Guid>(type: "uuid", nullable: true),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uuid", nullable: true),
DeletionTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Books",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Isbn = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Price = table.Column<decimal>(type: "numeric(18,2)", nullable: false),
AuthorId = table.Column<Guid>(type: "uuid", nullable: false),
IsBorrowed = table.Column<bool>(type: "boolean", nullable: false),
CreationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
CreatorId = table.Column<Guid>(type: "uuid", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
LastModifierId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Books", x => x.Id);
table.ForeignKey(
name: "FK_Books_Authors_AuthorId",
column: x => x.AuthorId,
principalTable: "Authors",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
// 创建索引
migrationBuilder.CreateIndex(
name: "IX_Authors_Email",
table: "Authors",
column: "Email",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Books_AuthorId",
table: "Books",
column: "AuthorId");
migrationBuilder.CreateIndex(
name: "IX_Books_Isbn",
table: "Books",
column: "Isbn",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// 回滚操作,按与Up()相反的顺序删除表和索引
migrationBuilder.DropIndex(name: "IX_Books_Isbn", table: "Books");
migrationBuilder.DropIndex(name: "IX_Books_AuthorId", table: "Books");
migrationBuilder.DropTable(name: "Books");
migrationBuilder.DropIndex(name: "IX_Authors_Email", table: "Authors");
migrationBuilder.DropTable(name: "Authors");
}
}
3.1.5.3 执行迁移
使用EF Core CLI或Package Manager Console执行迁移:
# 使用EF Core CLI执行迁移
dotnet ef database update –project BookStore.EntityFrameworkCore –startup-project BookStore.Web
# 回滚到特定迁移
dotnet ef database update InitialCreate –project BookStore.EntityFrameworkCore –startup-project BookStore.Web
# 删除最新迁移
dotnet ef migrations remove –project BookStore.EntityFrameworkCore –startup-project BookStore.Web
3.1.5.4 PostgreSQL特定迁移配置
对于PostgreSQL数据库,迁移命令与其他数据库类似,但需要注意一些特定配置:
# 使用ABP CLI生成PostgreSQL迁移
abp ef migrations add Initial –database-provider PostgreSQL –connection-string-name Default
# 或使用EF Core CLI生成PostgreSQL迁移
dotnet ef migrations add Initial –project BookStore.EntityFrameworkCore –startup-project BookStore.Web –provider Npgsql.EntityFrameworkCore.PostgreSQL
# 使用ABP CLI更新PostgreSQL数据库
abp ef database update –database-provider PostgreSQL –connection-string-name Default
# 或使用EF Core CLI更新PostgreSQL数据库
dotnet ef database update –project BookStore.EntityFrameworkCore –startup-project BookStore.Web –provider Npgsql.EntityFrameworkCore.PostgreSQL
对于PostgreSQL,可以在迁移中使用特定的配置,例如序列、索引等:
protected override void Up(MigrationBuilder migrationBuilder)
{
// 创建序列
migrationBuilder.CreateSequence<int>(name: "BookSequence", startValue: 1000L);
// 创建表并使用序列
migrationBuilder.CreateTable(
name: "Books",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "uuid_generate_v4()"),
Code = table.Column<string>(type: "text", nullable: false, defaultValueSql: "'BOOK-' || nextval('" + migrationBuilder.SqlGenerationHelper.EscapeName("BookSequence") + "')"),
Name = table.Column<string>(type: "text", nullable: false),
ExtraInfo = table.Column<string>(type: "jsonb", nullable: true) // 使用PostgreSQL的jsonb类型
},
constraints: table =>
{
table.PrimaryKey("PK_Books", x => x.Id);
});
// 创建PostgreSQL特定的索引
migrationBuilder.CreateIndex(
name: "IX_Books_Name",
table: "Books",
column: "Name")
.Annotation("Npgsql:IndexMethod", "gin"); // 使用PostgreSQL的GIN索引
}
- PostgreSQL对标识符的大小写敏感,建议使用蛇形命名法(snake_case)
- 对于大型表,使用CONCURRENTLY选项创建索引可以避免锁表
- 对于JSON数据,使用jsonb类型而不是json类型,因为jsonb支持索引和更高效的查询
- 使用uuid_generate_v4()函数生成UUID主键
- 对于自增列,使用序列(Sequence)而不是IDENTITY列,因为序列更灵活
# 使用ABP CLI更新PostgreSQL数据库
abp ef database update –database-provider PostgreSQL –connection-string-name Default
# 或使用EF Core CLI更新PostgreSQL数据库
dotnet ef database update –project BookStore.EntityFrameworkCore –startup-project BookStore.Web –provider Npgsql.EntityFrameworkCore.PostgreSQL
3.1.5.5 PostgreSQL性能优化
PostgreSQL有一些特定的性能优化技巧,可以在ABP项目中使用:
PostgreSQL支持连接池,可以提高应用程序的性能:
// 在连接字符串中配置连接池
{
"ConnectionStrings": {
"Default": "Host=localhost;Database=BookStore;Username=postgres;Password=your_password;Pooling=true;MinPoolSize=5;MaxPoolSize=20"
}
}
PostgreSQL支持多种索引类型,根据数据类型选择合适的索引类型:
- B-tree索引:适合大多数数据类型和查询
- GIN索引:适合JSON、数组等复杂数据类型
- GiST索引:适合地理数据、范围数据等
- Hash索引:适合等值查询
对于大型表,可以使用分区表来提高查询性能:
// 创建分区表
migrationBuilder.Sql(@"
CREATE TABLE IF NOT EXISTS Orders (
Id uuid NOT NULL,
OrderDate date NOT NULL,
Amount decimal(18,2) NOT NULL,
PRIMARY KEY (Id, OrderDate)
) PARTITION BY RANGE (OrderDate);
— 创建分区
CREATE TABLE IF NOT EXISTS Orders_2023 PARTITION OF Orders
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE IF NOT EXISTS Orders_2024 PARTITION OF Orders
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
");
对于复杂的查询,可以使用物化视图来提高性能:
// 创建物化视图
migrationBuilder.Sql(@"
CREATE MATERIALIZED VIEW IF NOT EXISTS BookStatistics AS
SELECT
Type,
COUNT(*) AS Count,
AVG(Price) AS AveragePrice,
SUM(Price) AS TotalPrice
FROM Books
GROUP BY Type;
— 创建索引
CREATE INDEX IF NOT EXISTS IX_BookStatistics_Type ON BookStatistics(Type);
— 创建刷新物化视图的函数
CREATE OR REPLACE FUNCTION RefreshBookStatistics()
RETURNS void AS $$
BEGIN
REFRESH MATERIALIZED VIEW BookStatistics;
END;
$$ LANGUAGE plpgsql;
");
对于大量数据的插入、更新或删除,使用批量操作可以提高性能:
// 使用EF Core的批量操作
public async Task BulkInsertBooksAsync(List<Book> books)
{
var dbContext = await GetDbContextAsync();
// 使用AddRange而不是多次Add
dbContext.AddRange(books);
// 批量保存
await dbContext.SaveChangesAsync();
}
// 或使用Npgsql的批量复制功能
public async Task BulkCopyBooksAsync(List<Book> books)
{
var dbContext = await GetDbContextAsync();
var connection = (NpgsqlConnection)dbContext.Database.GetDbConnection();
await connection.OpenAsync();
// 使用Npgsql的批量复制功能
using (var writer = connection.BeginBinaryImport("COPY Books (Id, Name, Type, Price) FROM STDIN (FORMAT BINARY)"))
{
foreach (var book in books)
{
writer.StartRow();
writer.Write(book.Id);
writer.Write(book.Name);
writer.Write(book.Type);
writer.Write(book.Price);
}
await writer.CompleteAsync();
}
await connection.CloseAsync();
}
PostgreSQL提供了多种监控和调试工具,可以在ABP项目中使用:
// 查询当前活动的连接
public async Task<List<object>> GetActiveConnectionsAsync()
{
var dbContext = await GetDbContextAsync();
return await dbContext.Database.SqlQueryRaw<object>(@"
SELECT
pid,
usename,
datname,
application_name,
client_addr,
state,
query
FROM pg_stat_activity
WHERE state = 'active'
").ToListAsync();
}
// 查询慢查询
public async Task<List<object>> GetSlowQueriesAsync()
{
var dbContext = await GetDbContextAsync();
return await dbContext.Database.SqlQueryRaw<object>(@"
SELECT
queryid,
query,
calls,
total_time,
mean_time,
max_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10
").ToListAsync();
}
3.1.5.5 PostgreSQL性能优化
PostgreSQL有一些特定的性能优化技巧,可以在ABP项目中使用:
PostgreSQL支持连接池,可以提高应用程序的性能:
// 在连接字符串中配置连接池
{
"ConnectionStrings": {
"Default": "Host=localhost;Database=BookStore;Username=postgres;Password=your_password;Pooling=true;MinPoolSize=5;MaxPoolSize=20"
}
}
PostgreSQL支持多种索引类型,根据数据类型选择合适的索引类型:
- B-tree索引:适合大多数数据类型和查询
- GIN索引:适合JSON、数组等复杂数据类型
- GiST索引:适合地理数据、范围数据等
- Hash索引:适合等值查询
对于大型表,可以使用分区表来提高查询性能:
// 创建分区表
migrationBuilder.Sql(@"
CREATE TABLE IF NOT EXISTS Orders (
Id uuid NOT NULL,
OrderDate date NOT NULL,
Amount decimal(18,2) NOT NULL,
PRIMARY KEY (Id, OrderDate)
) PARTITION BY RANGE (OrderDate);
— 创建分区
CREATE TABLE IF NOT EXISTS Orders_2023 PARTITION OF Orders
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE IF NOT EXISTS Orders_2024 PARTITION OF Orders
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
");
对于复杂的查询,可以使用物化视图来提高性能:
// 创建物化视图
migrationBuilder.Sql(@"
CREATE MATERIALIZED VIEW IF NOT EXISTS BookStatistics AS
SELECT
Type,
COUNT(*) AS Count,
AVG(Price) AS AveragePrice,
SUM(Price) AS TotalPrice
FROM Books
GROUP BY Type;
— 创建索引
CREATE INDEX IF NOT EXISTS IX_BookStatistics_Type ON BookStatistics(Type);
— 创建刷新物化视图的函数
CREATE OR REPLACE FUNCTION RefreshBookStatistics()
RETURNS void AS $$
BEGIN
REFRESH MATERIALIZED VIEW BookStatistics;
END;
$$ LANGUAGE plpgsql;
");
对于大量数据的插入、更新或删除,使用批量操作可以提高性能:
// 使用EF Core的批量操作
public async Task BulkInsertBooksAsync(List<Book> books)
{
var dbContext = await GetDbContextAsync();
// 使用AddRange而不是多次Add
dbContext.AddRange(books);
// 批量保存
await dbContext.SaveChangesAsync();
}
// 或使用Npgsql的批量复制功能
public async Task BulkCopyBooksAsync(List<Book> books)
{
var dbContext = await GetDbContextAsync();
var connection = (NpgsqlConnection)dbContext.Database.GetDbConnection();
await connection.OpenAsync();
// 使用Npgsql的批量复制功能
using (var writer = connection.BeginBinaryImport("COPY Books (Id, Name, Type, Price) FROM STDIN (FORMAT BINARY)"))
{
foreach (var book in books)
{
writer.StartRow();
writer.Write(book.Id);
writer.Write(book.Name);
writer.Write(book.Type);
writer.Write(book.Price);
}
await writer.CompleteAsync();
}
await connection.CloseAsync();
}
3.1.5.6 PostgreSQL监控和调试
PostgreSQL提供了多种监控和调试工具,可以在ABP项目中使用:
配置PostgreSQL日志可以帮助调试问题:
// 在appsettings.json中配置EF Core日志
{
"Logging": {
"LogLevel": {
"Microsoft.EntityFrameworkCore.Database.Command": "Information" // 记录SQL命令
}
}
}
PostgreSQL提供了多种性能视图,可以监控数据库的性能:
// 查询当前活动的连接
public async Task<List<object>> GetActiveConnectionsAsync()
{
var dbContext = await GetDbContextAsync();
return await dbContext.Database.SqlQueryRaw<object>(@"
SELECT
pid,
usename,
datname,
application_name,
client_addr,
state,
query
FROM pg_stat_activity
WHERE state = 'active'
").ToListAsync();
}
// 查询慢查询
public async Task<List<object>> GetSlowQueriesAsync()
{
var dbContext = await GetDbContextAsync();
return await dbContext.Database.SqlQueryRaw<object>(@"
SELECT
queryid,
query,
calls,
total_time,
mean_time,
max_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10
").ToListAsync();
}
可以使用PostgreSQL的EXPLAIN命令分析查询计划:
// 分析查询计划
public async Task<string> GetQueryPlanAsync(string sql)
{
var dbContext = await GetDbContextAsync();
var result = await dbContext.Database.SqlQueryRaw<string>(@$"
EXPLAIN (FORMAT JSON) {sql}
").FirstOrDefaultAsync();
return result;
}
- pgAdmin:PostgreSQL官方提供的图形化管理工具
- pgBadger:PostgreSQL日志分析工具
- Prometheus + Grafana:监控PostgreSQL性能指标
- pg_stat_monitor:PostgreSQL查询监控扩展
3.1.6 种子数据管理
种子数据是指应用程序启动时需要初始化的基础数据,如默认用户、角色、配置等。
3.1.6.1 实现种子数据贡献者
创建种子数据贡献者,用于初始化基础数据:
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using BookStore.Domain.Authors;
using BookStore.Domain.Books;
namespace BookStore.EntityFrameworkCore
{
public class BookStoreDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Author, Guid> _authorRepository;
private readonly IRepository<Book, Guid> _bookRepository;
public BookStoreDataSeedContributor(
IRepository<Author, Guid> authorRepository,
IRepository<Book, Guid> bookRepository)
{
_authorRepository = authorRepository;
_bookRepository = bookRepository;
}
public async Task SeedAsync(DataSeedContext context)
{
// 初始化作者数据
await SeedAuthorsAsync();
// 初始化图书数据
await SeedBooksAsync();
}
private async Task SeedAuthorsAsync()
{
// 检查是否已存在作者数据
if (await _authorRepository.GetCountAsync() > 0)
{
return; // 已存在数据,跳过
}
// 创建初始作者
var authors = new[]
{
new Author("张三", "zhangsan@example.com", new DateTime(1980, 1, 1)),
new Author("李四", "lisi@example.com", new DateTime(1985, 2, 2)),
new Author("王五", "wangwu@example.com", new DateTime(1990, 3, 3)),
new Author("赵六", "zhaoliu@example.com", new DateTime(1995, 4, 4))
};
await _authorRepository.InsertManyAsync(authors);
}
private async Task SeedBooksAsync()
{
// 检查是否已存在图书数据
if (await _bookRepository.GetCountAsync() > 0)
{
return; // 已存在数据,跳过
}
// 获取所有作者
var authors = await _authorRepository.GetListAsync();
// 创建初始图书
var books = new[]
{
new Book("ASP.NET Core实战", "9787115526666", 89.00m, authors[0].Id),
new Book("ABP框架入门到精通", "9787115526667", 99.00m, authors[1].Id),
new Book("领域驱动设计实践", "9787115526668", 79.00m, authors[2].Id),
new Book("微服务架构设计", "9787115526669", 109.00m, authors[3].Id),
new Book("Entity Framework Core指南", "9787115526670", 89.00m, authors[0].Id),
new Book("C# 10.0核心特性", "9787115526671", 79.00m, authors[1].Id)
};
await _bookRepository.InsertManyAsync(books);
}
}
}
3.1.6.2 配置种子数据
在模块中配置种子数据:
using Volo.Abp.Modularity;
using Volo.Abp.EntityFrameworkCore;using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace BookStore.EntityFrameworkCore
{
[DependsOn(
typeof(BookStoreDomainModule),
typeof(AbpEntityFrameworkCoreModule)
)]
public class BookStoreEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 注册DbContext
context.Services.AddAbpDbContext<BookStoreDbContext>(options =>
{
options.AddDefaultRepositories();
});
// 配置EF Core
Configure<AbpDbContextOptions>(options =>
{
options.Configure<BookStoreDbContext>(dbContextOptions =>
{
dbContextOptions.UseNpgsql();
});
});
}
}
}
3.1.7 企业级最佳实践
3.1.7.1 实体设计最佳实践
3.1.7.2 数据库设计最佳实践
3.1.7.3 迁移和种子数据最佳实践
3.1.8 实际案例:图书管理系统中的实体与数据库设计
3.1.8.1 实体关系设计
在图书管理系统中,主要实体包括:
- 图书(Book):表示系统中的图书,包含名称、ISBN、价格等属性
- 作者(Author):表示图书的作者,包含姓名、邮箱、出生日期等属性
- 借阅记录(BorrowingRecord):表示图书的借阅和归还记录
- 用户(User):表示系统用户,使用ABP框架的内置用户系统
实体关系图:
User 1 <– * BorrowingRecord * –> 1 Book * –> 1 Author
3.1.8.2 完整实体实现
// 图书实体
public class Book : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public string Isbn { get; private set; }
public decimal Price { get; private set; }
public Guid AuthorId { get; private set; }
public bool IsBorrowed { get; private set; }
public Author Author { get; private set; }
protected Book() { }
public Book(string name, string isbn, decimal price, Guid authorId)
{
Name = name;
Isbn = isbn;
Price = price;
AuthorId = authorId;
IsBorrowed = false;
}
// 业务方法
public void MarkAsBorrowed()
{
if (IsBorrowed)
{
throw new BusinessException("BookAlreadyBorrowed");
}
IsBorrowed = true;
}
public void MarkAsReturned()
{
if (!IsBorrowed)
{
throw new BusinessException("BookNotBorrowed");
}
IsBorrowed = false;
}
}
// 作者实体
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public string Email { get; private set; }
public DateTime BirthDate { get; private set; }
public List<Book> Books { get; private set; } = new List<Book>();
protected Author() { }
public Author(string name, string email, DateTime birthDate)
{
Name = name;
Email = email;
BirthDate = birthDate;
}
}
// 借阅记录实体
public class BorrowingRecord : FullAuditedAggregateRoot<Guid>
{
public Guid BookId { get; private set; }
public Guid UserId { get; private set; }
public DateTime BorrowDate { get; private set; }
public DateTime? ReturnDate { get; private set; }
public string Remarks { get; private set; }
public Book Book { get; private set; }
public IdentityUser User { get; private set; }
protected BorrowingRecord() { }
public BorrowingRecord(Guid bookId, Guid userId, string remarks = null)
{
BookId = bookId;
UserId = userId;
BorrowDate = DateTime.Now;
ReturnDate = null;
Remarks = remarks;
}
// 业务方法
public void ReturnBook()
{
if (ReturnDate.HasValue)
{
throw new BusinessException("BookAlreadyReturned");
}
ReturnDate = DateTime.Now;
}
}
3.1.9 实践练习
3.1.10 总结与进阶指南
3.1.10.1 本章总结
- 实体是领域驱动设计中的核心概念,代表业务领域中的具体事物
- ABP框架提供了一系列实体基类,简化了实体的创建和管理
- 实体设计应遵循封装原则,将业务逻辑封装在实体内部
- 数据库映射可以通过数据注解或Fluent API实现
- 迁移文件用于管理数据库模式变更,支持应用和回滚
- 种子数据用于初始化应用程序的基础数据
- 企业级开发需要遵循实体设计、数据库设计和迁移管理的最佳实践
3.1.10.2 进阶学习资源
通过本章的学习,你已经掌握了ABP框架中实体与数据库设计的核心概念和最佳实践。在实际开发中,合理的实体和数据库设计是构建高质量应用程序的基础,能够提高应用程序的性能、可维护性和可扩展性。
下一章,我们将学习应用服务开发,了解如何实现业务逻辑和数据传输对象(DTOs)。



