C#/.NET 微服务架构:从入门到精通(四):数据持久化(Entity Framework Core + MySQL)

系列前言

前一篇我们完成了 Git 代码管理的规范化落地,为微服务协作开发打下了基础。而在微服务架构中,数据持久化是核心业务能力的支撑 —— 每个微服务通常拥有独立的数据库,通过 ORM 框架实现高效的数据操作。

Entity Framework Core(EF Core)作为.NET 官方的轻量级、跨平台 ORM,配合 MySQL 这一开源主流关系型数据库,是.NET 微服务数据持久化的黄金组合。本篇将聚焦EF Core + MySQL 在微服务中的实战应用,从环境搭建、实体建模、迁移部署到 CRUD 操作,手把手实现微服务的数据层。

一、微服务架构下的数据持久化特点

与单体应用不同,微服务的数据持久化有明确的边界隔离要求:

  • 独立数据库:每个微服务(如用户服务、订单服务)拥有独立的 MySQL 数据库,避免耦合;
  • ORM 轻量化:EF Core 的轻量特性适配微服务的 “小而专”,无需重型 ORM;
  • 迁移可控:通过 EF Core 迁移(Migrations)实现数据库版本管理,适配微服务频繁迭代;
  • 依赖注入友好:EF Core 天然支持.NET 依赖注入,与微服务架构无缝集成。

二、环境准备:EF Core + MySQL 依赖安装

如何在 Alibaba Cloud Linux上安装 MySQL可以看一下我之前发布的文章:https://www.cnblogs.com/lantingxu/p/19863386

我们以商品Api(ProductApi) 为例,演示微服务的数据持久化搭建。

1、必需的 NuGet 包

在商品Api项目中安装以下包(.NET 8 环境下,推荐版本匹配):

# EF Core核心包
dotnet add package Microsoft.EntityFrameworkCore --version 8.0.0
# MySQL EF Core提供程序(推荐Pomelo,社区维护更活跃)
dotnet add package Pomelo.EntityFrameworkCore.MySql --version 8.0.0
# EF Core工具包(用于数据库迁移)
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8.0.0
# 设计时支持(Visual Studio/CLI迁移必需)
dotnet add package Microsoft.EntityFrameworkCore.Design --version 8.0.0

 也可以通过包管理器手动安装

image

 包安装成功后:

image

 三、实战步骤 1:实体建模与 DbContext 配置

1. 创建领域实体(Entity)

在商品Api项目中新建Entities文件夹,添加Product实体:

namespace ProductApi.Entities;

/// <summary>
/// 商品
/// </summary>
public class Product
{
    /// <summary>
    /// Id
    /// </summary>
    public long Id { get; set; }

    /// <summary>
    /// 商品名称
    /// </summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// 价格
    /// </summary>
    public decimal Price { get; set; }

    /// <summary>
    /// 库存
    /// </summary>
    public int Stock { get; set; }

    /// <summary>
    /// 描述
    /// </summary>
    public string Description { get; set; } = string.Empty;

    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTime CreatedAt { get; set; } = DateTime.Now;

    /// <summary>
    /// 软删除标记
    /// </summary>
    public bool IsDeleted { get; set; } = false;
}

2. 创建微服务专属 DbContext

新建DbContexts文件夹,添加ProductDbContext

using Microsoft.EntityFrameworkCore;
using ProductApi.Entities;

namespace ProductApi.DbContexts;

/// <summary>
/// 商品数据库上下文
/// </summary>
public class ProductDbContext : DbContext
{
    public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options) { }

    public DbSet<Product> Products => Set<Product>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>(entity =>
        {
            entity.HasKey(p => p.Id);
            entity.Property(p => p.Name).IsRequired().HasMaxLength(200);
            entity.Property(p => p.Price).HasPrecision(18, 2);
            entity.Property(p => p.Description).HasMaxLength(1000);

            // 初始化测试数据
            entity.HasData(
                new Product { Id = 1, Name = "iPhone 16 Pro", Price = 8999, Stock = 100, Description = "苹果旗舰手机" },
                new Product { Id = 2, Name = "华为Mate 70 Pro", Price = 7999, Stock = 200, Description = "华为旗舰手机" }
            );
        });
    }
}

3. 配置连接字符串与依赖注入

在商品Api的appsettings.Development.json中添加 MySQL 连接字符串:

{
  "ConnectionStrings": {
    "ProductDb": "server=你的数据库地址;port=3306;database=ProductDb;user=你的账号;password=你的密码;Character Set=utf8mb4;"
  }
}

Program.cs中注册ProductDbContext(微服务依赖注入核心):

using Microsoft.EntityFrameworkCore;
using ProductApi.DbContexts;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//注册EF Core DbContext(连接MySQL)
var connectionString = builder.Configuration.GetConnectionString("ProductDb");
builder.Services.AddDbContext<ProductDbContext>(options =>
{
    options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString),
        mysqlOpt => mysqlOpt.MigrationsAssembly(typeof(ProductDbContext).Assembly.FullName));
});


var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();

四、实战步骤 2:数据库迁移(Migrations)

EF Core 迁移是微服务数据库版本管理的核心,无需手动写 SQL,通过代码自动生成 / 更新数据库。

1. 安装 EF Core CLI 工具(首次必做)

终端执行:

dotnet tool install --global dotnet-ef

验证安装:

dotnet ef --version

2. 生成迁移文件

在ProductApi项目根目录执行:

# 生成迁移(名称自定义,如InitialCreate)
dotnet ef migrations add InitialCreate

image

执行后会生成Migrations文件夹,包含迁移代码(自动生成的建表 SQL 逻辑)。

image

 3. 应用迁移到数据库

# 将迁移应用到MySQL数据库
dotnet ef database update

image

此时查看 MySQL 数据库,会自动生成Users表和__EFMigrationsHistory迁移历史表。

image

 4. 添加对应注释

可以看到我们的实体中是有注释的,但是数据库表中却没有

image

image

 现在我们就来完成将实体中的字段描述在迁移的过程中带入到数据库表字段的注释:

1、右键ProductApi项目,选择属性,然后找到输出,勾选文档文件

image

2、微服务存在多个项目,迁移时都需要添加注释,新增一个EntityFramework的共享类库(Commerce.Share.EntityFramework)

在类库下需要安装以下包:

dotnet add package Microsoft.EntityFrameworkCore --version 8.0.0
dotnet add package Microsoft.EntityFrameworkCore.Relational --version 8.0.0

image

  3、在Commerce.Share.EntityFramework中添加一个静态类ModelBuilderExtensions:

using System.Reflection;
using System.Xml.Linq;
using Microsoft.EntityFrameworkCore;

namespace Commerce.Share.EntityFramework;

/// <summary>
/// ModelBuilder扩展方法
/// </summary>
public static class ModelBuilderExtensions
{
    /// <summary>
    /// 应用XML注释到数据库
    /// </summary>
    /// <param name="modelBuilder"></param>
    /// <param name="assembly"></param>
    public static void ApplyXmlCommentsToDatabase(this ModelBuilder modelBuilder, Assembly assembly)
    {
        var xmlPath = Path.ChangeExtension(assembly.Location, ".xml");
        if (!File.Exists(xmlPath)) return;

        var xmlDoc = XDocument.Load(xmlPath);
        var entityTypes = assembly.GetTypes()
            .Where(t => t.IsClass && !t.IsAbstract && t.Namespace?.Contains("Entities") == true);

        foreach (var entityType in entityTypes)
        {
            // 表注释
            var classComment = GetSummary(xmlDoc, $"T:{entityType.FullName}");
            if (!string.IsNullOrWhiteSpace(classComment))
            {
                modelBuilder.Entity(entityType).ToTable(t => t.HasComment(classComment));
            }

            // 字段注释
            foreach (var prop in entityType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
            {
                var propComment = GetSummary(xmlDoc, $"P:{entityType.FullName}.{prop.Name}");
                if (!string.IsNullOrWhiteSpace(propComment))
                {
                    modelBuilder.Entity(entityType).Property(prop.Name).HasComment(propComment);
                }
            }
        }
    }

    /// <summary>
    /// 获取XML注释
    /// </summary>
    /// <param name="xmlDoc"></param>
    /// <param name="memberName"></param>
    /// <returns></returns>
    private static string? GetSummary(XDocument xmlDoc, string memberName)
    {
        try
        {
            return xmlDoc.Descendants("member")
                .FirstOrDefault(m => m.Attribute("name")?.Value == memberName)
                ?.Element("summary")?.Value.Trim();
        }
        catch { return null; }
    }
}

4、ProductApi项目添加Commerce.Share.EntityFramework项目的引用

image

 5、修改ProductDbContext.cs文件

using Microsoft.EntityFrameworkCore;
using ProductApi.Entities;
using Commerce.Share.EntityFramework; //本次修改:添加引用

namespace ProductApi.DbContexts;

/// <summary>
/// 商品数据库上下文
/// </summary>
public class ProductDbContext : DbContext
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="options"></param>
    public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options) { }

    /// <summary>
    /// DbSet 声明
    /// </summary>
    public DbSet<Product> Products  { get; set; }

    /// <summary>
    /// 构建数据库模型
    /// </summary>
    /// <param name="modelBuilder"></param>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 自动读取实体类的XML注释,生成数据库注释(本次修改)
        modelBuilder.ApplyXmlCommentsToDatabase(typeof(Product).Assembly);
        modelBuilder.Entity<Product>(entity =>
        {
            entity.HasKey(p => p.Id);
            entity.Property(p => p.Name).IsRequired().HasMaxLength(200);
            entity.Property(p => p.Price).HasPrecision(18, 2);
            entity.Property(p => p.Description).HasMaxLength(1000);

            // 配置软删除查询过滤器(自动过滤已删除数据)(本次修改)
            modelBuilder.Entity<Product>()
                .HasQueryFilter(u => !u.IsDeleted);

            // 初始化测试数据
            entity.HasData(
                new Product { Id = 1, Name = "iPhone 16 Pro", Price = 8999, Stock = 100, Description = "苹果旗舰手机" },
                new Product { Id = 2, Name = "华为Mate 70 Pro", Price = 7999, Stock = 200, Description = "华为旗舰手机" }
            );
        });
    }
}

6、在ProductApi项目根目录重新执行迁移命令

dotnet ef migrations add annotation
dotnet ef database update

7、刷新一下数据库就可以看到注释了

image

 五、实战步骤 3:微服务 CRUD 操作实现

在ProductApi中的Controllers文件夹添加ProductController,通过依赖注入使用ProductDbContext

using Microsoft.AspNetCore.Mvc;
using ProductApi.DbContexts;
using ProductApi.Entities;
using Microsoft.EntityFrameworkCore;

namespace ProductApi.Controllers
{
    /// <summary>
    /// 商品接口
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private readonly ProductDbContext _context;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="context"></param>
        public ProductController(ProductDbContext context)
        {
            _context = context;
        }

        /// <summary>
        /// 获取所有商品
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
        {
            // AsNoTracking:查询优化,不跟踪实体状态(微服务读多写少场景推荐)
            return await _context.Products.AsNoTracking().ToListAsync();
        }

        /// <summary>
        /// 获取单个商品
        /// </summary>
        /// <param name="id">主键Id</param>
        /// <returns></returns>
        [HttpGet("{id}")]
        public async Task<ActionResult<Product>> GetProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null) return NotFound();
            return product;
        }

        /// <summary>
        /// 新增商品
        /// </summary>
        /// <param name="product">商品</param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult<Product>> PostProduct(Product product)
        {
            _context.Products.Add(product);
            await _context.SaveChangesAsync();
            return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
        }

        /// <summary>
        /// 更新商品
        /// </summary>
        /// <param name="id">主键Id</param>
        /// <param name="product">商品</param>
        /// <returns></returns>
        [HttpPut("{id}")]
        public async Task<IActionResult> PutProduct(int id, Product product)
        {
            if (id != product.Id) return BadRequest();
            _context.Entry(product).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(id)) return NotFound();
                else throw;
            }
            return NoContent();
        }

        /// <summary>
        /// 删除商品
        /// </summary>
        /// <param name="id">主键Id</param>
        /// <returns></returns>
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null) return NotFound();

            // 软删除:仅标记IsDeleted,不物理删除
            product.IsDeleted = true;
            await _context.SaveChangesAsync();
            return NoContent();
        }

        private bool ProductExists(int id)
        {
            return _context.Products.Any(e => e.Id == id);
        }
    }
}

启动项目,可以看到一个Swagger接口文档

image

 来操作一下看看:

  • 查看商品列表

    image

  • 新增商品

    image

  • 更新商品

    image

  • 获取单个商品详情

    image

  • 删除商品

    image

    image

六、微服务数据持久化最佳实践

1. 微服务数据库隔离原则

  • 每个微服务独立数据库:用户服务用UserServiceDb,订单服务用OrderServiceDb,禁止跨库直接查询;
  • 跨服务数据交互:通过 API 调用或领域事件(如集成事件)实现,避免数据库耦合。

2. EF Core 性能优化

  • 读操作使用AsNoTracking():减少 EF Core 状态跟踪开销,适合微服务高频查询场景;
  • 合理配置索引:通过 Fluent API 或数据注解为常用查询字段(如UserNameEmail)添加索引;
  • 避免 N+1 查询:使用Include()预加载关联数据(如订单服务中Include(o => o.OrderItems))。

3. 迁移与部署规范

  • 迁移文件提交 Git:迁移代码是数据库版本的 “文档”,需随微服务代码一起管理;
  • 生产环境迁移谨慎:推荐先在预发布环境验证,或使用dotnet ef migrations script生成 SQL 脚本手动执行;

4. 数据安全与软删除

  • 软删除替代物理删除:如示例中IsDeleted字段,配合 EF Core 查询过滤器自动过滤已删除数据,便于数据恢复;
  • 敏感数据加密:密码等敏感字段需哈希存储(如使用BCrypt.Net-Next库),禁止明文存数据库。

七、常见问题解决

1. MySQL 版本与 Pomelo 不匹配

错误提示:Method not found: 'Void Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping...'

解决:确保Pomelo.EntityFrameworkCore.MySql版本与 EF Core 版本一致(如均为 8.0.0),并在UseMySql中指定正确的MySqlServerVersion

2. 迁移失败:“无法连接到数据库”

检查:

  • 连接字符串中的ServerUserPassword是否正确;
  • MySQL 服务是否启动;
  • 防火墙是否允许 3306 端口访问。

八、总结

本篇我们完成了EF Core + MySQL 在.NET 微服务中的全流程落地:

  1. 搭建 EF Core + MySQL 环境,安装必需的 NuGet 包;
  2. 完成微服务实体建模、DbContext 配置与依赖注入;
  3. 通过 EF Core 迁移实现数据库版本管理,自动生成表结构;
  4. 实现微服务 CRUD 接口,结合软删除、查询过滤器等最佳实践;
  5. 梳理微服务数据持久化的隔离原则、性能优化与安全规范。
数据持久化是微服务业务能力的基石,规范化的 EF Core 使用能让微服务的数据层更高效、可维护。
posted @ 2026-04-28 14:53  挺秃然的i  阅读(96)  评论(0)    收藏  举报