欢迎光临
我们一直在努力

3.1 ABP Business版 10.0实战指南:实体与数据库设计最佳实践

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进行数据库访问,支持多种数据库映射方式:

  • 数据注解:使用特性进行映射配置
  • Fluent API:使用代码进行映射配置
  • 约定优于配置:EF 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,需要进行以下配置:

  • 添加PostgreSQL依赖:
  • dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
    dotnet add package Volo.Abp.EntityFrameworkCore.PostgreSql

  • 配置PostgreSQL连接字符串:
  • 在appsettings.json文件中添加PostgreSQL连接字符串:

    {
    "ConnectionStrings": {
    "Default": "Host=localhost;Database=BookStore;Username=postgres;Password=your_password"
    }
    }

  • 配置PostgreSQL数据库提供程序:
  • 在模块类中配置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项目中充分利用:

  • UUID类型:
  • // 使用PostgreSQL的UUID类型作为主键
    public class Book : AuditedEntity<Guid>
    {
    public Guid Id { get; set; } // EF Core会自动映射到PostgreSQL的UUID类型
    // 其他属性…
    }

  • JSONB类型:
  • // 使用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; }
    }

  • HStore类型:
  • // 使用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; }
    }

  • Range类型:
  • // 使用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,需要在项目中添加以下配置:

  • 添加PostgreSQL依赖:
  • dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
    dotnet add package Volo.Abp.EntityFrameworkCore.PostgreSql

  • 配置PostgreSQL连接字符串:
  • 在appsettings.json文件中添加PostgreSQL连接字符串:

    {
    "ConnectionStrings": {
    "Default": "Host=localhost;Database=BookStore;Username=postgres;Password=your_password"
    }
    }

  • 配置PostgreSQL数据库提供程序:
  • 在模块类中配置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框架中充分利用:

  • UUID类型:
  • // 使用PostgreSQL的UUID类型作为主键
    public class Book : AuditedEntity<Guid>
    {
    public Guid Id { get; set; } // EF Core会自动映射到PostgreSQL的UUID类型
    // 其他属性…
    }

  • JSONB类型:
  • // 使用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; }
    }

  • HStore类型:
  • // 使用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; }
    }

  • Range类型:
  • // 使用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 迁移文件结构

    生成的迁移文件包含两个部分:

  • 迁移类:包含Up()和Down()方法,分别用于应用和回滚迁移
  • 模型快照:记录当前数据库模型的状态,用于检测模型变更
  • // 迁移类示例
    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数据库,迁移命令与其他数据库类似,但需要注意一些特定配置:

  • 生成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

  • 更新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特定的迁移配置:
  • 对于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迁移注意事项:
    • PostgreSQL对标识符的大小写敏感,建议使用蛇形命名法(snake_case)
    • 对于大型表,使用CONCURRENTLY选项创建索引可以避免锁表
    • 对于JSON数据,使用jsonb类型而不是json类型,因为jsonb支持索引和更高效的查询
    • 使用uuid_generate_v4()函数生成UUID主键
    • 对于自增列,使用序列(Sequence)而不是IDENTITY列,因为序列更灵活
  • 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

    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性能监控:
  • 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的日志功能:
  • 配置PostgreSQL日志可以帮助调试问题:

    // 在appsettings.json中配置EF Core日志
    {
    "Logging": {
    "LogLevel": {
    "Microsoft.EntityFrameworkCore.Database.Command": "Information" // 记录SQL命令
    }
    }
    }

  • 使用PostgreSQL的性能视图:
  • 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的分析功能:
  • 可以使用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;
    }

  • 使用PostgreSQL监控工具:
    • 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 实体设计最佳实践
  • 使用适当的实体基类:根据业务需求选择合适的实体基类,如FullAuditedAggregateRoot<TKey>包含完整的审计信息和软删除功能
  • 封装业务逻辑:将业务规则和行为封装在实体内部,保持数据完整性
  • 使用私有设置器:实体属性使用私有设置器,通过方法修改状态,确保业务规则得到执行
  • 合理设计实体关系:避免过度复杂的实体关系,使用适当的删除行为(如Cascade、Restrict、SetNull)
  • 使用值对象:将相关属性组合成值对象,提高代码的可读性和可维护性
  • 避免大实体:将大实体拆分为多个小实体,提高性能和可维护性
  • 3.1.7.2 数据库设计最佳实践
  • 使用适当的数据类型:为每个属性选择合适的数据类型,如使用decimal(18,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 实践练习

  • 创建实体:创建图书管理系统的实体,包括图书、作者、借阅记录等
  • 配置数据库映射:使用Fluent API配置实体映射,包括表名、列名、索引等
  • 生成迁移文件:生成迁移文件并执行,创建数据库表
  • 实现种子数据:创建种子数据贡献者,初始化基础数据
  • 测试实体关系:测试实体之间的关系,确保数据完整性
  • 优化实体设计:根据最佳实践优化实体设计,如使用值对象、封装业务逻辑等
  • 3.1.10 总结与进阶指南

    3.1.10.1 本章总结
    • 实体是领域驱动设计中的核心概念,代表业务领域中的具体事物
    • ABP框架提供了一系列实体基类,简化了实体的创建和管理
    • 实体设计应遵循封装原则,将业务逻辑封装在实体内部
    • 数据库映射可以通过数据注解或Fluent API实现
    • 迁移文件用于管理数据库模式变更,支持应用和回滚
    • 种子数据用于初始化应用程序的基础数据
    • 企业级开发需要遵循实体设计、数据库设计和迁移管理的最佳实践
    3.1.10.2 进阶学习资源
  • ABP框架实体文档
  • Entity Framework Core文档
  • 领域驱动设计:软件核心复杂性应对之道
  • 数据库设计范式
  • EF Core迁移最佳实践
  • 通过本章的学习,你已经掌握了ABP框架中实体与数据库设计的核心概念和最佳实践。在实际开发中,合理的实体和数据库设计是构建高质量应用程序的基础,能够提高应用程序的性能、可维护性和可扩展性。

    下一章,我们将学习应用服务开发,了解如何实现业务逻辑和数据传输对象(DTOs)。

    赞(0)
    未经允许不得转载:171主机测评 » 3.1 ABP Business版 10.0实战指南:实体与数据库设计最佳实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址