HaniBlindBox/docs/数据库迁移详细文档/阶段4-应用程序集成和EF Core适配.md
2026-01-02 01:00:52 +08:00

27 KiB
Raw Blame History

阶段4应用程序集成和EF Core适配

阶段概述

时间: 1天
目标: 配置.NET 8应用程序连接SQL Server创建EF Core实体模型适配API接口
优先级: P0 (最高优先级)

详细任务清单

4.1 EF Core 8.0配置 (0.3天)

任务描述

配置Entity Framework Core 8.0连接SQL Server数据库

具体工作

  • 安装EF Core SQL Server包
  • 配置连接字符串
  • 创建DbContext
  • 配置依赖注入

EF Core配置

1. NuGet包安装
<!-- 在.csproj文件中添加 -->
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
2. 连接字符串配置
// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=honey_box;Trusted_Connection=true;TrustServerCertificate=true;MultipleActiveResultSets=true;Encrypt=false",
    "ReadOnlyConnection": "Server=localhost;Database=honey_box;User Id=honey_box_readonly;Password=HoneyBoxRead2024!@#;TrustServerCertificate=true;Encrypt=false"
  },
  "DatabaseSettings": {
    "CommandTimeout": 30,
    "EnableSensitiveDataLogging": false,
    "EnableDetailedErrors": false
  }
}

// appsettings.Development.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=honey_box_dev;Trusted_Connection=true;TrustServerCertificate=true;MultipleActiveResultSets=true;Encrypt=false"
  },
  "DatabaseSettings": {
    "EnableSensitiveDataLogging": true,
    "EnableDetailedErrors": true
  }
}
3. DbContext配置
// Data/HoneyBoxDbContext.cs
using Microsoft.EntityFrameworkCore;
using HoneyBox.Core.Entities;

namespace HoneyBox.Data
{
    public class HoneyBoxDbContext : DbContext
    {
        public HoneyBoxDbContext(DbContextOptions<HoneyBoxDbContext> options) : base(options)
        {
        }

        // 用户相关
        public DbSet<User> Users { get; set; }
        public DbSet<UserAccount> UserAccounts { get; set; }
        public DbSet<UserLoginLog> UserLoginLogs { get; set; }

        // 商品相关
        public DbSet<Goods> Goods { get; set; }
        public DbSet<GoodsItem> GoodsItems { get; set; }
        public DbSet<GoodsType> GoodsTypes { get; set; }
        public DbSet<GoodsExtension> GoodsExtensions { get; set; }

        // 订单相关
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderItem> OrderItems { get; set; }

        // 财务相关
        public DbSet<BalanceTransaction> BalanceTransactions { get; set; }
        public DbSet<IntegralTransaction> IntegralTransactions { get; set; }
        public DbSet<PaymentRecord> PaymentRecords { get; set; }

        // 优惠券和任务
        public DbSet<CouponTemplate> CouponTemplates { get; set; }
        public DbSet<UserCoupon> UserCoupons { get; set; }
        public DbSet<TaskConfig> TaskConfigs { get; set; }
        public DbSet<UserTaskRecord> UserTaskRecords { get; set; }

        // 系统配置
        public DbSet<PrizeLevel> PrizeLevels { get; set; }
        public DbSet<SystemConfig> SystemConfigs { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // 应用所有配置
            modelBuilder.ApplyConfigurationsFromAssembly(typeof(HoneyBoxDbContext).Assembly);

            // 全局查询过滤器
            modelBuilder.Entity<User>().HasQueryFilter(u => u.Status != 0);
            modelBuilder.Entity<Goods>().HasQueryFilter(g => g.DeletedAt == null);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                // 仅在未配置时使用默认连接
                optionsBuilder.UseSqlServer("Server=localhost;Database=honey_box;Trusted_Connection=true;");
            }
        }
    }
}
4. 依赖注入配置
// Program.cs
using Microsoft.EntityFrameworkCore;
using HoneyBox.Data;

var builder = WebApplication.CreateBuilder(args);

// 数据库配置
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var databaseSettings = builder.Configuration.GetSection("DatabaseSettings");

builder.Services.AddDbContext<HoneyBoxDbContext>(options =>
{
    options.UseSqlServer(connectionString, sqlOptions =>
    {
        sqlOptions.CommandTimeout(databaseSettings.GetValue<int>("CommandTimeout", 30));
        sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 3,
            maxRetryDelay: TimeSpan.FromSeconds(5),
            errorNumbersToAdd: null);
    });

    if (builder.Environment.IsDevelopment())
    {
        options.EnableSensitiveDataLogging(databaseSettings.GetValue<bool>("EnableSensitiveDataLogging", false));
        options.EnableDetailedErrors(databaseSettings.GetValue<bool>("EnableDetailedErrors", false));
    }
});

// 只读数据库上下文(用于报表查询)
builder.Services.AddDbContext<HoneyBoxReadOnlyDbContext>(options =>
{
    var readOnlyConnection = builder.Configuration.GetConnectionString("ReadOnlyConnection");
    options.UseSqlServer(readOnlyConnection, sqlOptions =>
    {
        sqlOptions.CommandTimeout(60); // 报表查询超时时间更长
    });
});

var app = builder.Build();

// 数据库迁移(仅在开发环境)
if (app.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<HoneyBoxDbContext>();
    context.Database.EnsureCreated();
}

app.Run();

4.2 实体模型创建 (0.4天)

任务描述

创建符合snake_case数据库结构的EF Core实体模型

具体工作

  • 创建实体类
  • 配置实体映射
  • 设置导航属性
  • 配置索引和约束

实体模型设计

1. 用户实体
// Entities/User.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace HoneyBox.Core.Entities
{
    [Table("users")]
    public class User
    {
        [Key]
        [Column("id")]
        public int Id { get; set; }

        [Required]
        [MaxLength(50)]
        [Column("open_id")]
        public string OpenId { get; set; } = string.Empty;

        [MaxLength(255)]
        [Column("union_id")]
        public string? UnionId { get; set; }

        [MaxLength(255)]
        [Column("gzh_open_id")]
        public string? GzhOpenId { get; set; }

        [MaxLength(15)]
        [Column("mobile")]
        public string? Mobile { get; set; }

        [Required]
        [MaxLength(255)]
        [Column("nickname")]
        public string Nickname { get; set; } = string.Empty;

        [Required]
        [MaxLength(500)]
        [Column("head_img")]
        public string HeadImg { get; set; } = string.Empty;

        [Column("parent_id")]
        public int ParentId { get; set; } = 0;

        [Column("money", TypeName = "decimal(18,2)")]
        public decimal Money { get; set; } = 0;

        [Column("money2", TypeName = "decimal(18,2)")]
        public decimal Money2 { get; set; } = 0;

        [Column("integral", TypeName = "decimal(18,2)")]
        public decimal Integral { get; set; } = 0;

        [Column("score", TypeName = "decimal(18,2)")]
        public decimal Score { get; set; } = 0;

        [Column("ou_qi")]
        public int OuQi { get; set; } = 0;

        [Column("ou_qi_level")]
        public int OuQiLevel { get; set; } = 0;

        [Column("vip_level")]
        public byte VipLevel { get; set; } = 1;

        [Column("status")]
        public byte Status { get; set; } = 1;

        [Column("is_test")]
        public int IsTest { get; set; } = 0;

        [Required]
        [MaxLength(16)]
        [Column("uid")]
        public string Uid { get; set; } = string.Empty;

        [Column("click_id")]
        public int? ClickId { get; set; }

        [Column("created_at")]
        public DateTime CreatedAt { get; set; } = DateTime.Now;

        [Column("updated_at")]
        public DateTime UpdatedAt { get; set; } = DateTime.Now;

        [Column("last_login_at")]
        public DateTime? LastLoginAt { get; set; }

        // 导航属性
        public virtual UserAccount? Account { get; set; }
        public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
        public virtual ICollection<UserCoupon> Coupons { get; set; } = new List<UserCoupon>();
        public virtual ICollection<UserTaskRecord> TaskRecords { get; set; } = new List<UserTaskRecord>();
    }
}
2. 商品实体
// Entities/Goods.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace HoneyBox.Core.Entities
{
    [Table("goods")]
    public class Goods
    {
        [Key]
        [Column("id")]
        public int Id { get; set; }

        [Column("category_id")]
        public int CategoryId { get; set; } = 0;

        [Required]
        [MaxLength(255)]
        [Column("title")]
        public string Title { get; set; } = string.Empty;

        [Required]
        [MaxLength(500)]
        [Column("img_url")]
        public string ImgUrl { get; set; } = string.Empty;

        [Required]
        [MaxLength(500)]
        [Column("img_url_detail")]
        public string ImgUrlDetail { get; set; } = string.Empty;

        [Column("price", TypeName = "decimal(18,2)")]
        public decimal Price { get; set; } = 0;

        [Column("stock")]
        public int Stock { get; set; } = 0;

        [Column("sale_stock")]
        public int SaleStock { get; set; } = 0;

        [Column("type")]
        public byte Type { get; set; } = 0;

        [Column("status")]
        public byte Status { get; set; } = 1;

        [Column("is_lock")]
        public byte IsLock { get; set; } = 0;

        [Column("lock_time")]
        public int LockTime { get; set; } = 0;

        [Column("is_discount")]
        public byte IsDiscount { get; set; } = 0;

        [Column("is_new")]
        public byte IsNew { get; set; } = 0;

        [Column("is_show")]
        public byte IsShow { get; set; } = 0;

        [Column("unlock_amount", TypeName = "decimal(18,2)")]
        public decimal UnlockAmount { get; set; } = 0;

        [Column("daily_limit")]
        public int DailyLimit { get; set; } = 0;

        [Column("global_limit")]
        public int GlobalLimit { get; set; } = 0;

        [Column("sort_order")]
        public int SortOrder { get; set; } = 1;

        [Column("sale_time")]
        public DateTime? SaleTime { get; set; }

        [Column("created_at")]
        public DateTime CreatedAt { get; set; } = DateTime.Now;

        [Column("updated_at")]
        public DateTime UpdatedAt { get; set; } = DateTime.Now;

        [Column("deleted_at")]
        public DateTime? DeletedAt { get; set; }

        // 导航属性
        public virtual ICollection<GoodsItem> Items { get; set; } = new List<GoodsItem>();
        public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
        public virtual GoodsExtension? Extension { get; set; }
    }
}
3. 订单实体
// Entities/Order.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace HoneyBox.Core.Entities
{
    [Table("orders")]
    public class Order
    {
        [Key]
        [Column("id")]
        public int Id { get; set; }

        [Required]
        [MaxLength(60)]
        [Column("order_no")]
        public string OrderNo { get; set; } = string.Empty;

        [Column("user_id")]
        public int UserId { get; set; }

        [Column("goods_id")]
        public int GoodsId { get; set; }

        [Column("box_number")]
        public int BoxNumber { get; set; }

        [Required]
        [MaxLength(255)]
        [Column("goods_title")]
        public string GoodsTitle { get; set; } = string.Empty;

        [MaxLength(500)]
        [Column("goods_img_url")]
        public string? GoodsImgUrl { get; set; }

        [Column("order_type")]
        public byte OrderType { get; set; } = 0;

        [Column("order_total", TypeName = "decimal(18,2)")]
        public decimal OrderTotal { get; set; }

        [Column("discount_total", TypeName = "decimal(18,2)")]
        public decimal DiscountTotal { get; set; }

        [Column("final_price", TypeName = "decimal(18,2)")]
        public decimal FinalPrice { get; set; }

        [Column("used_money", TypeName = "decimal(18,2)")]
        public decimal UsedMoney { get; set; } = 0;

        [Column("used_integral", TypeName = "decimal(18,2)")]
        public decimal UsedIntegral { get; set; } = 0;

        [Column("used_money2", TypeName = "decimal(18,2)")]
        public decimal UsedMoney2 { get; set; } = 0;

        [Column("coupon_id")]
        public int? CouponId { get; set; }

        [Column("used_coupon", TypeName = "decimal(18,2)")]
        public decimal UsedCoupon { get; set; } = 0;

        [Column("draw_count")]
        public int DrawCount { get; set; } = 0;

        [Column("pay_type")]
        public byte PayType { get; set; } = 1;

        [Column("status")]
        public byte Status { get; set; } = 0;

        [Column("click_id")]
        public int? ClickId { get; set; }

        [Column("created_at")]
        public DateTime CreatedAt { get; set; } = DateTime.Now;

        [Column("paid_at")]
        public DateTime? PaidAt { get; set; }

        // 导航属性
        [ForeignKey("UserId")]
        public virtual User User { get; set; } = null!;

        [ForeignKey("GoodsId")]
        public virtual Goods Goods { get; set; } = null!;

        public virtual ICollection<OrderItem> Items { get; set; } = new List<OrderItem>();
    }
}

4.3 实体配置类 (0.2天)

任务描述

创建Fluent API配置类精确控制实体映射

具体工作

  • 创建实体配置类
  • 配置索引和约束
  • 设置外键关系
  • 配置查询过滤器

实体配置

1. 用户配置
// Configurations/UserConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using HoneyBox.Core.Entities;

namespace HoneyBox.Data.Configurations
{
    public class UserConfiguration : IEntityTypeConfiguration<User>
    {
        public void Configure(EntityTypeBuilder<User> builder)
        {
            builder.ToTable("users");

            // 主键
            builder.HasKey(e => e.Id);
            builder.Property(e => e.Id).ValueGeneratedOnAdd();

            // 唯一索引
            builder.HasIndex(e => e.OpenId).IsUnique().HasDatabaseName("ix_users_open_id");
            builder.HasIndex(e => e.Uid).IsUnique().HasDatabaseName("ix_users_uid");

            // 普通索引
            builder.HasIndex(e => e.Mobile).HasDatabaseName("ix_users_mobile");
            builder.HasIndex(e => e.UnionId).HasDatabaseName("ix_users_union_id");
            builder.HasIndex(e => new { e.Status, e.CreatedAt }).HasDatabaseName("ix_users_status_created");
            builder.HasIndex(e => e.ParentId).HasDatabaseName("ix_users_parent_id");

            // 字段配置
            builder.Property(e => e.OpenId).IsRequired().HasMaxLength(50);
            builder.Property(e => e.UnionId).HasMaxLength(255);
            builder.Property(e => e.GzhOpenId).HasMaxLength(255);
            builder.Property(e => e.Mobile).HasMaxLength(15);
            builder.Property(e => e.Nickname).IsRequired().HasMaxLength(255);
            builder.Property(e => e.HeadImg).IsRequired().HasMaxLength(500);
            builder.Property(e => e.Uid).IsRequired().HasMaxLength(16);

            // 默认值
            builder.Property(e => e.ParentId).HasDefaultValue(0);
            builder.Property(e => e.Money).HasDefaultValue(0).HasColumnType("decimal(18,2)");
            builder.Property(e => e.Money2).HasDefaultValue(0).HasColumnType("decimal(18,2)");
            builder.Property(e => e.Integral).HasDefaultValue(0).HasColumnType("decimal(18,2)");
            builder.Property(e => e.Score).HasDefaultValue(0).HasColumnType("decimal(18,2)");
            builder.Property(e => e.OuQi).HasDefaultValue(0);
            builder.Property(e => e.OuQiLevel).HasDefaultValue(0);
            builder.Property(e => e.VipLevel).HasDefaultValue((byte)1);
            builder.Property(e => e.Status).HasDefaultValue((byte)1);
            builder.Property(e => e.IsTest).HasDefaultValue(0);
            builder.Property(e => e.CreatedAt).HasDefaultValueSql("GETDATE()");
            builder.Property(e => e.UpdatedAt).HasDefaultValueSql("GETDATE()");

            // 导航属性
            builder.HasOne(e => e.Account)
                .WithOne(a => a.User)
                .HasForeignKey<UserAccount>(a => a.UserId)
                .OnDelete(DeleteBehavior.Cascade);

            builder.HasMany(e => e.Orders)
                .WithOne(o => o.User)
                .HasForeignKey(o => o.UserId)
                .OnDelete(DeleteBehavior.Restrict);
        }
    }
}
2. 商品配置
// Configurations/GoodsConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using HoneyBox.Core.Entities;

namespace HoneyBox.Data.Configurations
{
    public class GoodsConfiguration : IEntityTypeConfiguration<Goods>
    {
        public void Configure(EntityTypeBuilder<Goods> builder)
        {
            builder.ToTable("goods");

            // 主键
            builder.HasKey(e => e.Id);
            builder.Property(e => e.Id).ValueGeneratedOnAdd();

            // 索引
            builder.HasIndex(e => new { e.Type, e.Status }).HasDatabaseName("ix_goods_type_status");
            builder.HasIndex(e => new { e.Status, e.SortOrder }).HasDatabaseName("ix_goods_status_sort");
            builder.HasIndex(e => e.CategoryId).HasDatabaseName("ix_goods_category_id");
            builder.HasIndex(e => e.CreatedAt).HasDatabaseName("ix_goods_created_at");
            builder.HasIndex(e => e.UnlockAmount).HasDatabaseName("ix_goods_unlock_amount");

            // 字段配置
            builder.Property(e => e.Title).IsRequired().HasMaxLength(255);
            builder.Property(e => e.ImgUrl).IsRequired().HasMaxLength(500);
            builder.Property(e => e.ImgUrlDetail).IsRequired().HasMaxLength(500);

            // 默认值
            builder.Property(e => e.CategoryId).HasDefaultValue(0);
            builder.Property(e => e.Price).HasDefaultValue(0).HasColumnType("decimal(18,2)");
            builder.Property(e => e.Stock).HasDefaultValue(0);
            builder.Property(e => e.SaleStock).HasDefaultValue(0);
            builder.Property(e => e.Type).HasDefaultValue((byte)0);
            builder.Property(e => e.Status).HasDefaultValue((byte)1);
            builder.Property(e => e.IsLock).HasDefaultValue((byte)0);
            builder.Property(e => e.LockTime).HasDefaultValue(0);
            builder.Property(e => e.IsDiscount).HasDefaultValue((byte)0);
            builder.Property(e => e.IsNew).HasDefaultValue((byte)0);
            builder.Property(e => e.IsShow).HasDefaultValue((byte)0);
            builder.Property(e => e.UnlockAmount).HasDefaultValue(0).HasColumnType("decimal(18,2)");
            builder.Property(e => e.DailyLimit).HasDefaultValue(0);
            builder.Property(e => e.GlobalLimit).HasDefaultValue(0);
            builder.Property(e => e.SortOrder).HasDefaultValue(1);
            builder.Property(e => e.CreatedAt).HasDefaultValueSql("GETDATE()");
            builder.Property(e => e.UpdatedAt).HasDefaultValueSql("GETDATE()");

            // 软删除查询过滤器
            builder.HasQueryFilter(e => e.DeletedAt == null);

            // 导航属性
            builder.HasMany(e => e.Items)
                .WithOne(i => i.Goods)
                .HasForeignKey(i => i.GoodsId)
                .OnDelete(DeleteBehavior.Cascade);

            builder.HasOne(e => e.Extension)
                .WithOne(ext => ext.Goods)
                .HasForeignKey<GoodsExtension>(ext => ext.GoodsId)
                .OnDelete(DeleteBehavior.Cascade);
        }
    }
}

4.4 API接口适配 (0.1天)

任务描述

更新API控制器以使用新的EF Core上下文

具体工作

  • 更新控制器依赖注入
  • 修改数据访问代码
  • 适配查询语法
  • 测试API接口

API适配示例

1. 用户控制器适配
// Controllers/UserController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using HoneyBox.Data;
using HoneyBox.Core.Entities;

namespace HoneyBox.Api.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class UserController : ControllerBase
    {
        private readonly HoneyBoxDbContext _context;
        private readonly ILogger<UserController> _logger;

        public UserController(HoneyBoxDbContext context, ILogger<UserController> logger)
        {
            _context = context;
            _logger = logger;
        }

        [HttpGet("{openId}")]
        public async Task<ActionResult<User>> GetUser(string openId)
        {
            var user = await _context.Users
                .Include(u => u.Account)
                .FirstOrDefaultAsync(u => u.OpenId == openId);

            if (user == null)
            {
                return NotFound();
            }

            return user;
        }

        [HttpPost("login")]
        public async Task<ActionResult<User>> Login([FromBody] LoginRequest request)
        {
            var user = await _context.Users
                .FirstOrDefaultAsync(u => u.OpenId == request.OpenId);

            if (user == null)
            {
                // 创建新用户
                user = new User
                {
                    OpenId = request.OpenId,
                    UnionId = request.UnionId,
                    Nickname = request.Nickname,
                    HeadImg = request.HeadImg,
                    Uid = GenerateUid(),
                    CreatedAt = DateTime.Now,
                    UpdatedAt = DateTime.Now
                };

                _context.Users.Add(user);
                await _context.SaveChangesAsync();
            }
            else
            {
                // 更新用户信息
                user.Nickname = request.Nickname;
                user.HeadImg = request.HeadImg;
                user.LastLoginAt = DateTime.Now;
                user.UpdatedAt = DateTime.Now;

                await _context.SaveChangesAsync();
            }

            return user;
        }

        [HttpGet("{userId}/balance")]
        public async Task<ActionResult<object>> GetBalance(int userId)
        {
            var user = await _context.Users
                .Where(u => u.Id == userId)
                .Select(u => new
                {
                    u.Money,
                    u.Money2,
                    u.Integral,
                    u.Score
                })
                .FirstOrDefaultAsync();

            if (user == null)
            {
                return NotFound();
            }

            return user;
        }

        private string GenerateUid()
        {
            return "U" + DateTime.Now.ToString("yyyyMMddHHmmss") + Random.Shared.Next(1000, 9999);
        }
    }

    public class LoginRequest
    {
        public string OpenId { get; set; } = string.Empty;
        public string? UnionId { get; set; }
        public string Nickname { get; set; } = string.Empty;
        public string HeadImg { get; set; } = string.Empty;
    }
}
2. 商品控制器适配
// Controllers/GoodsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using HoneyBox.Data;
using HoneyBox.Core.Entities;

namespace HoneyBox.Api.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GoodsController : ControllerBase
    {
        private readonly HoneyBoxDbContext _context;

        public GoodsController(HoneyBoxDbContext context)
        {
            _context = context;
        }

        [HttpGet]
        public async Task<ActionResult<IEnumerable<Goods>>> GetGoods(
            [FromQuery] int page = 1,
            [FromQuery] int pageSize = 20,
            [FromQuery] byte? type = null)
        {
            var query = _context.Goods
                .Where(g => g.Status == 1 && g.IsShow == 1);

            if (type.HasValue)
            {
                query = query.Where(g => g.Type == type.Value);
            }

            var goods = await query
                .OrderByDescending(g => g.SortOrder)
                .ThenByDescending(g => g.CreatedAt)
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .ToListAsync();

            return goods;
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<Goods>> GetGoods(int id)
        {
            var goods = await _context.Goods
                .Include(g => g.Items.Where(i => i.SurplusStock > 0))
                .Include(g => g.Extension)
                .FirstOrDefaultAsync(g => g.Id == id);

            if (goods == null)
            {
                return NotFound();
            }

            return goods;
        }

        [HttpGet("{id}/items")]
        public async Task<ActionResult<IEnumerable<GoodsItem>>> GetGoodsItems(int id, int boxNumber)
        {
            var items = await _context.GoodsItems
                .Where(i => i.GoodsId == id && i.BoxNumber == boxNumber)
                .OrderBy(i => i.SortOrder)
                .ToListAsync();

            return items;
        }
    }
}

验收标准

配置验收

  • EF Core 8.0包安装成功
  • 连接字符串配置正确
  • DbContext创建并注册成功
  • 依赖注入配置完成

实体模型验收

  • 所有实体类创建完成
  • 实体配置类配置正确
  • 导航属性设置完成
  • 索引和约束配置正确

API适配验收

  • 控制器依赖注入更新完成
  • 数据访问代码适配完成
  • API接口测试通过
  • 查询性能满足要求

风险点和注意事项

配置风险

  1. 连接字符串错误: 可能导致连接失败
  2. EF Core版本兼容性: 确保版本匹配
  3. 依赖注入配置: 生命周期管理要正确
  4. 数据库权限: 确保应用程序有足够权限

实体映射风险

  1. 字段映射错误: 可能导致数据读取失败
  2. 数据类型不匹配: 精度丢失或溢出
  3. 导航属性循环引用: 序列化问题
  4. 查询过滤器: 可能影响预期查询结果

解决方案

  1. 分步测试: 每个组件完成后立即测试
  2. 日志记录: 详细记录EF Core查询日志
  3. 单元测试: 为关键功能编写单元测试
  4. 性能监控: 监控查询性能和数据库连接

下一阶段准备

为阶段5准备的内容

  • 功能测试用例
  • 性能基准测试
  • 压力测试工具
  • 监控指标配置

交接文档

  • EF Core配置文档
  • 实体模型设计文档
  • API适配说明
  • 测试验证报告

阶段4完成标志: EF Core 8.0配置完成实体模型创建成功API接口适配完成应用程序可以正常连接和操作SQL Server数据库。