【开源】跨语言·跨平台·跨数据库(1) —— 用宝技术轻量框架 AI 开发指南

【开源】跨语言·跨平台·跨数据库(1) —— 用宝技术轻量框架 AI 开发指南

把本文档直接丢给 AI,即可自动生成 C#/Java/ArkTS 三端完整 CRUD 代码

💡 一句话说清本文价值:把这份文档全文复制给 AI,它就能根据你的表结构,自动生成 C#、Java、华为鸿蒙ArkTS 三端完整 CRUD 代码。我们的底层架构是封装好的 DLL 可直接调用,也可以根据文档直接生成。同理,Java、ArkTS 都可以直接生成底层、WebAPI、服务层及前端全部代码。

作者:周方勇/咏方舟-长江支流(金质打印通、用宝框架开源作者)

简述:用宝框架(UserBaoTech)是一套轻量级企业级分层架构基架,.NET Standard 2.0,零依赖,支持 MySQL/SQL Server/Oracle/SQLite,SQL就是最好的跨平台,依据标准 SQL 自动生成,可跨语言无缝迁移至鸿蒙 ArkTS 及 Java 技术栈,接口名、类名、方法签名三端完全一致

在这里插入图片描述

🔁 跨语言迁移能力说明

核心结论:完全一样,不是"接近",不是"类似"

用宝框架的设计与语言无关,其核心是一组接口契约 + 模板方法 + 手写 SQL 的抽象体系。从 Java 的语言能力和我们现有 C# 代码的写法来看,可以实现 接口名、类名、方法签名三端完全一样点击AI前端生成产品CRUD见博文

具体表现在以下几个方面:

1. 语法层面:完全对等

C#(用宝框架)Java 对应ArkTS 对应
IEntity<TKey>IEntity<TKey>IEntity<TKey>
IRepository<T>IRepository<T>IRepository<T>
RepositoryBase<T>RepositoryBase<T>RepositoryBase<T>
ServiceBase<TParam,TResult>ServiceBase<TParam,TResult>ServiceBase<TParam,TResult>
KeyServiceBase<TResult>KeyServiceBase<TResult>KeyServiceBase<TResult>
MessageResult<T>MessageResult<T>MessageResult<T>
QueryParametersBaseQueryParametersBaseQueryParametersBase
IDataAccessExecutorIDataAccessExecutorIDataAccessExecutor
async/await Task<T>CompletableFuture<T>Promise<T>
Dictionary<string, object>Map<String, Object>Record<string, object>
using(var ...)try-with-resourcesusing(ArkTS 支持)

2. 架构层面:设计模式完全移植

用宝框架的核心设计模式:

  • 仓储模式(Repository)
  • 工作单元(UnitOfWork)
  • 模板方法模式(ServiceBase / KeyServiceBase)
  • 依赖注入(DI)

这些模式在 Java 和 ArkTS 中都有对应的标准实现方式,且写法几乎一模一样。

3. 数据访问层:只有底层驱动不同

C# 用宝框架Java 对应ArkTS 对应
接口抽象IDataAccessExecutorIDataAccessExecutorIDataAccessExecutor
连接对象IDbConnectionConnection(JDBC)relationalStore
命令对象IDbCommandPreparedStatementrelationalStore API
读取器IDataReaderResultSetResultSet
参数占位符@参数名?#{}(MyBatis)?(适配中)
事务管理IDbTransactionConnection.setAutoCommit(false)relationalStore 事务
方言处理switch(DatabaseType)switch(DatabaseType)switch(DatabaseType)

除了底层驱动和占位符写法外,上层业务代码一字不改。

4. 代码可直接"翻译",结构完全一样

ProductRepository 为例:

C#:

protected override string GetTableNameCore() => "Products";
protected override Product MapToEntity(IDataReader reader) { ... }
protected override IDictionary<string, object> GetInsertValues(Product entity) { ... }

Java:

@Override
protected String getTableNameCore() { return "Products"; }
@Override
protected Product mapToEntity(ResultSet rs) { ... }
@Override
protected Map<String, Object> getInsertValues(Product entity) { ... }

ArkTS:

protected getTableNameCore(): string { return "Products"; }
protected mapToEntity(rs: ResultSet): Product { ... }
protected getInsertValues(entity: Product): Record<string, object> { ... }

结构完全一样,只是语法大小写和数据类型名称不同。但完全可以和 C# 保持完全一样的统一命名风格。

5. 迁移后的一致性体验

用户拿到 Java 版或 ArkTS 版用宝框架后,看到的会是:

维度三端一致的内容
接口名IRepository<T>IEntity<TKey>IService<TParam, TResult>
基类名RepositoryBase<T>ServiceBase<TParam, TResult>KeyServiceBase<TResult>
返回结构MessageResult<T>
查询参数QueryParametersBase
业务写法GetXxxListServiceGetXxxByIdKeyServiceDeleteXxxKeyService
Controller 风格C#: [HttpGet("{id}")] / Java: @GetMapping("/{id}") / ArkTS: 装饰器风格

也就是说,一个熟悉用宝框架 C# 版的开发者,切换到 Java 或 ArkTS 版时,只需要适应语言语法差异,架构认知完全迁移,学习成本几乎为零。

总结

用宝框架的代码可以无缝对应到 Java 和 ArkTS:

  • 接口/类名:完全相同
  • 继承关系:完全相同
  • 方法签名:完全相同
  • 业务写法:完全相同
  • 底层驱动:仅替换数据访问实现

完全一样,不是"接近",不是"类似"。这是用宝框架作为"用户宝贝技术"的核心价值所在。

💡 把这份文档全文复制给 AI,它就能根据你的表结构,自动生成 C#、Java、ArkTS 三端完整 CRUD 代码。框架底层已封装好,也可根据文档直接生成。一次设计,三端共用。

📌 本文档使用说明

用宝技术轻量框架是一个已编译好的现成类库(.NET Standard 2.0),作为 NuGet 包或 DLL 直接引用即可,不需要新建或生成其源码。

你只需:

  1. 新建 YourProject.Core(类库 .NET Standard 2.0)
  2. 新建 YourProject.WebAPI(ASP.NET Core .NET 10)
  3. 通过 NuGet 引用 UserBaoTech.Foundation(用宝框架)

然后,把本文档全文复制给 AI(或新对话框),AI 会根据你提供的表结构自动生成完整模块代码(Entity → Query → Repository → Service → Controller),无需再沟通架构背景

⚠️ 特别说明:文档内的接口和基类代码仅供理解底层机制,不是你要手动编写的内容。用宝框架已提供全部基架实现,你只需写业务代码。

目录

好的,现在开始输出 《UserBaoTech.Foundation 架构开发文档 · 最终完整版》

UserBaoTech.Foundation 架构开发文档

最终完整版

版本:v3.0
发布日期:2026年7月11日
适用范围:所有基于 UserBaoTech.Foundation 的新项目开发
阅读对象:开发人员、AI 编程助手、新项目初始化人员

第一部分:架构总览

1.1 这是什么?

UserBaoTech.Foundation 是一套轻量级企业级分层架构基架,核心目标:

  • 提供一套永不淘汰的底层数据访问框架(基于 .NET Standard 2.0)
  • 让业务代码干净、可测试、可跨语言迁移(C# ↔ 鸿蒙 ArkTS ↔ Java)
  • 不依赖任何重量级 ORM(无 EF、无 Dapper),手写 SQL,完全可控
  • 数据库方言适配(MySQL / SQL Server / Oracle / SQLite)自动处理

1.2 核心设计哲学

原则说明
SQL 是唯一标准手写 SQL,确保跨数据库、跨语言(C#/ArkTS/Java)完全一致
Foundation 是只读基架底层代码视为三方组件,业务层绝不越界修改
分层隔离业务层仅依赖接口(IRepositoryIService),不依赖具体实现
主键统一为 string兼容所有数据库(int/GUID/字符串),避免类型转换风险
参数前缀自动适配业务层写 @参数名,执行器自动转换为 :(Oracle)或 ?(ODBC)

1.3 三个项目边界

项目定位目标框架依赖
UserBaoTech.Foundation公司级技术基架(只读).NET Standard 2.0零三方依赖
YourProject.Core业务领域层(可写).NET Standard 2.0引用 Foundation
YourProject.WebAPI接入层(可写).NET 10引用 Foundation + Core

1.4 整体分层架构图

┌─────────────────────────────────────────────────────┐
│  表现层(WebAPI / WinForms / MAUI)                 │
│  - Controller 接收 HTTP 请求                       │
│  - 依赖注入 Service,不直接 new                    │
│  - 返回 MessageResult<T> 统一格式                  │
└─────────────────────────────────────────────────────┘
                        │ 调用
                        ▼
┌─────────────────────────────────────────────────────┐
│  业务服务层(Core.Services)                        │
│  - 继承 ServiceBase<TParam, TResult>               │
│  - 单参数主键场景继承 KeyServiceBase<TResult>       │
│  - 负责业务校验、事务控制、调用仓储                 │
│  - 纯 C# 逻辑,无 .NET 特有类型(可迁移 ArkTS/Java)│
└─────────────────────────────────────────────────────┘
                        │ 依赖接口
                        ▼
┌─────────────────────────────────────────────────────┐
│  仓储接口层(Foundation.Data.IRepository<T>)      │
│  - 定义 CRUD 契约(Query/GetById/Insert/Update/Delete)│
│  - 单泛型默认 string 主键,双泛型支持任意主键类型   │
└─────────────────────────────────────────────────────┘
                        │ 实现
                        ▼
┌─────────────────────────────────────────────────────┐
│  仓储实现层(Core.Data.XxxRepository)              │
│  - 继承 RepositoryBase<TEntity>                    │
│  - 必须实现:表名、MapToEntity、GetInsertValues、GetUpdateValues│
│  - 可重写:Delete(软删除)、GenerateId、NeedGetIdentityAfterInsert│
└─────────────────────────────────────────────────────┘
                        │ 内部使用
                        ▼
┌─────────────────────────────────────────────────────┐
│  轻量数据执行器(Foundation.DataAccessExecutor)    │
│  - 封装 ADO.NET(IDbConnection/IDbCommand/IDataReader)│
│  - 统一处理方言:参数前缀(@/:)、分页 SQL、自增 ID 回读│
│  - 上层(Repository)完全感知不到数据库差异          │
└─────────────────────────────────────────────────────┘
                        │ 连接
                        ▼
┌─────────────────────────────────────────────────────┐
│  物理数据库(MySQL / SQL Server / Oracle / SQLite)│
└─────────────────────────────────────────────────────┘

第二部分:从零创建项目

2.1 Visual Studio 2026 操作步骤

步骤 1:创建空白解决方案

打开 Visual Studio 2026 → 新建项目 → 搜索"空白解决方案" → 命名为 YourProject.sln → 创建。

步骤 2:添加类库项目(Core)

右键解决方案 → 添加 → 新建项目 → 搜索"类库 (.NET Standard)" → 命名为 YourProject.Core → 目标框架选择 .NET Standard 2.0 → 创建。

步骤 3:添加 WebAPI 项目

右键解决方案 → 添加 → 新建项目 → 搜索"ASP.NET Core Web API" → 命名为 YourProject.WebAPI → 目标框架 .NET 10 → 创建。

步骤 4:添加项目引用

右键 YourProject.WebAPI → 添加 → 项目引用 → 勾选 YourProject.Core → 确定。

步骤 5:测试 API 连通性

YourProject.WebAPIControllers 文件夹下创建 TestController.cs

using Microsoft.AspNetCore.Mvc;

namespace YourProject.WebAPI.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    [HttpGet("ping")]
    public IActionResult Ping()
    {
        return Ok(new { message = "API works!" });
    }
}

按 F5 启动,访问 https://localhost:xxxx/api/test/ping,返回 JSON 即成功。

第三部分:核心接口与基类

3.1 实体接口:IEntity

所有实体必须实现此接口。

using System;

namespace UserBaoTech.Foundation.Entities
{
    public interface IEntity<TKey>
    {
        TKey Id { get; set; }
        DateTime CreatedAt { get; set; }
        DateTime UpdatedAt { get; set; }
        bool IsDeleted { get; set; }   // 软删除标记
    }
}

实体实现示例:

using System;
using UserBaoTech.Foundation.Entities;

namespace YourProject.Core.Entities
{
    public class Product : IEntity<string>
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
        public bool IsDeleted { get; set; }
    }
}

3.2 查询参数基类:QueryParametersBase

提供分页、排序公共属性,子类仅需实现 GetFilters()

using System.Collections.Generic;

namespace UserBaoTech.Foundation.Data
{
    public abstract class QueryParametersBase : IQueryParameters
    {
        public int PageIndex { get; set; } = 1;      // 从 1 开始
        public int PageSize { get; set; } = 20;
        public string OrderBy { get; set; } = "CreatedAt";
        public bool IsAscending { get; set; } = false;
        public abstract IDictionary<string, object> GetFilters();
    }
}

查询参数实现示例:

using System.Collections.Generic;
using UserBaoTech.Foundation.Data;

namespace YourProject.Core.Models
{
    public class ProductQuery : QueryParametersBase
    {
        public string Name { get; set; }
        public decimal? MinPrice { get; set; }
        public decimal? MaxPrice { get; set; }
        public bool? IsDeleted { get; set; }

        public override IDictionary<string, object> GetFilters()
        {
            var dict = new Dictionary<string, object>();
            if (!string.IsNullOrEmpty(Name))
                dict["Name LIKE"] = $"%{Name}%";
            if (MinPrice.HasValue)
                dict["Price >= "] = MinPrice.Value;
            if (MaxPrice.HasValue)
                dict["Price <= "] = MaxPrice.Value;
            if (IsDeleted.HasValue)
                dict["IsDeleted"] = IsDeleted.Value;
            return dict;
        }
    }
}

3.3 仓储接口:IRepository

业务层仅依赖此接口。

using System.Collections.Generic;
using UserBaoTech.Foundation.Entities;

namespace UserBaoTech.Foundation.Data
{
    public interface IRepository<TEntity> where TEntity : IEntity<string>
    {
        string GetTableName();
        IEnumerable<TEntity> Query(string sql);
        IEnumerable<TEntity> Query(string sql, IDictionary<string, object> parameters);
        int Execute(string sql, IDictionary<string, object> parameters);
        TEntity GetById(string id);
        string Insert(TEntity entity);
        int Update(TEntity entity);
        int Delete(string id);
    }
}

3.4 仓储基类:RepositoryBase

子类继承后仅需实现四个抽象方法。

必须实现的方法:

protected abstract string GetTableNameCore();
protected abstract TEntity MapToEntity(IDataReader reader);
protected abstract IDictionary<string, object> GetInsertValues(TEntity entity);
protected abstract IDictionary<string, object> GetUpdateValues(TEntity entity);

可重写的虚方法:

方法默认值说明
GenerateId()Guid.NewGuid().ToString()程序生成主键
NeedGetIdentityAfterInsertfalse是否插入后回读自增 ID
GetIdFieldName()"Id"主键字段名
Delete(string id)物理删除可重写为软删除

仓储实现示例:

using System;
using System.Collections.Generic;
using System.Data;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;

namespace YourProject.Core.Data
{
    public class ProductRepository : RepositoryBase<Product>
    {
        public ProductRepository(IDataAccessExecutor executor) : base(executor) { }

        protected override string GetTableNameCore() => "Products";

        protected override Product MapToEntity(IDataReader reader)
        {
            return new Product
            {
                Id = reader["Id"].ToString(),
                Name = reader["Name"].ToString(),
                Price = Convert.ToDecimal(reader["Price"]),
                Stock = Convert.ToInt32(reader["Stock"]),
                CreatedAt = Convert.ToDateTime(reader["CreatedAt"]),
                UpdatedAt = Convert.ToDateTime(reader["UpdatedAt"]),
                IsDeleted = Convert.ToBoolean(reader["IsDeleted"])
            };
        }

        protected override IDictionary<string, object> GetInsertValues(Product entity)
        {
            return new Dictionary<string, object>
            {
                ["Id"] = entity.Id,
                ["Name"] = entity.Name,
                ["Price"] = entity.Price,
                ["Stock"] = entity.Stock,
                ["CreatedAt"] = DateTime.Now,
                ["UpdatedAt"] = DateTime.Now,
                ["IsDeleted"] = false
            };
        }

        protected override IDictionary<string, object> GetUpdateValues(Product entity)
        {
            return new Dictionary<string, object>
            {
                ["Name"] = entity.Name,
                ["Price"] = entity.Price,
                ["Stock"] = entity.Stock,
                ["UpdatedAt"] = DateTime.Now,
                ["IsDeleted"] = entity.IsDeleted
            };
        }

        // 软删除:重写 Delete 方法
        public override int Delete(string id)
        {
            var sql = $"UPDATE {GetTableName()} SET IsDeleted = 1 WHERE Id = @Id";
            return Execute(sql, new Dictionary<string, object> { { "@Id", id } });
        }
    }
}

第四部分:主键策略(三种模式)

4.1 模式一:程序生成 GUID(默认,推荐)

设置
GenerateId()默认(返回 GUID)
NeedGetIdentityAfterInsertfalse(默认)
GetInsertValues 是否包含 Id✅ 包含

特点:无需回读数据库,性能最优,跨数据库兼容性最好。

使用方式:子类无需任何额外配置,基类默认即可。

4.2 模式二:自定义格式主键

设置
GenerateId()重写,返回自定义格式
NeedGetIdentityAfterInsertfalse(默认)
GetInsertValues 是否包含 Id✅ 包含

示例:生成 PROD_XXXXXXXX 格式主键

protected override string GenerateId()
{
    return "PROD_" + Guid.NewGuid().ToString("N").Substring(0, 8).ToUpper();
}

4.3 模式三:数据库自增 INT

设置
GenerateId()返回 null
NeedGetIdentityAfterInserttrue
GetInsertValues 是否包含 Id❌ 不包含

特点:插入后自动执行 SELECT LAST_INSERT_ID() 回读 ID。

仓储实现示例:

public class ProductAutoIntRepository : ProductRepository
{
    protected override string GetTableNameCore() => "Products_AutoInt";

    protected override string GenerateId() => null;

    protected override bool NeedGetIdentityAfterInsert => true;

    protected override IDictionary<string, object> GetInsertValues(Product entity)
    {
        // 注意:不包含 Id 字段
        return new Dictionary<string, object>
        {
            ["Name"] = entity.Name,
            ["Price"] = entity.Price,
            ["Stock"] = entity.Stock,
            ["CreatedAt"] = DateTime.Now,
            ["UpdatedAt"] = DateTime.Now,
            ["IsDeleted"] = false
        };
    }
}

4.4 三种模式快速对比

模式主键生成方是否回读适用场景
程序 GUID程序(C#)❌ 否默认推荐,跨库兼容
自定义格式程序(重写)❌ 否需要业务前缀
自增 INT数据库✅ 是旧系统迁移或强制自增

第五部分:服务层(Service)

5.1 通用服务基类:ServiceBase<TParam, TResult>

封装日志、缓存、异常处理,子类仅需实现 DoExecuteAsync

using System.Threading.Tasks;
using UserBaoTech.Foundation.Infrastructure;

namespace UserBaoTech.Foundation.Services
{
    public abstract class ServiceBase<TParam, TResult> : IService<TParam, TResult>
        where TParam : class
        where TResult : class
    {
        protected readonly ILogger _logger;
        protected readonly ICacheProvider _cache;
        protected readonly IConfigProvider _config;

        protected ServiceBase(ILogger logger, ICacheProvider cache, IConfigProvider config)
        {
            _logger = logger ?? new NullLogger();
            _cache = cache ?? new NullCacheProvider();
            _config = config ?? new MemoryConfigProvider();
        }

        public virtual async Task<TResult> ExecuteAsync(TParam request)
        {
            // 自动处理:日志记录、缓存读写、异常捕获
            var result = await DoExecuteAsync(request);
            return result;
        }

        protected abstract Task<TResult> DoExecuteAsync(TParam request);
    }
}

5.2 主键操作专用基类:KeyServiceBase

针对"按 ID 查询/删除"场景,直接接收 KeyRequest

using System.Threading.Tasks;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Entities;
using UserBaoTech.Foundation.Infrastructure;

namespace UserBaoTech.Foundation.Services
{
    public abstract class KeyServiceBase<TResult> : ServiceBase<KeyRequest, TResult>
        where TResult : class
    {
        protected KeyServiceBase(ILogger logger, ICacheProvider cache, IConfigProvider config)
            : base(logger, cache, config) { }

        protected sealed override async Task<TResult> DoExecuteAsync(KeyRequest request)
        {
            if (request == null || string.IsNullOrEmpty(request.Id))
                throw new ArgumentException("主键不能为空");
            return await DoExecuteAsync(request.Id);
        }

        protected abstract Task<TResult> DoExecuteAsync(string id);

        // 默认删除实现(含钩子方法)
        protected async Task<MessageResult<bool>> DeleteAsync<TEntity>(
            IRepository<TEntity> repository, string id) where TEntity : IEntity<string>
        {
            var entity = repository.GetById(id);
            var (canDelete, msg) = CanDeleteWithMessage(entity);
            if (!canDelete)
                return new MessageResult<bool> { IsSuccess = false, Message = msg, Code = 0 };

            var rows = repository.Delete(id);
            if (rows > 0)
            {
                var (success, successMsg) = OnDeletedWithMessage(entity);
                return new MessageResult<bool> { IsSuccess = true, Message = successMsg ?? "删除成功", Data = true };
            }

            return new MessageResult<bool> { IsSuccess = false, Message = "删除失败", Code = 0 };
        }

        protected virtual (bool success, string message) CanDeleteWithMessage(IEntity<string> entity)
            => (entity != null && !entity.IsDeleted, entity == null ? "实体不存在" : "实体已被删除");

        protected virtual (bool success, string message) OnDeletedWithMessage(IEntity<string> entity)
            => (true, null);

        protected virtual (bool success, string message) OnDeleteFailedWithMessage(string id)
            => (false, null);
    }
}

5.3 KeyRequest 统一主键请求

所有单参数主键操作统一使用 KeyRequest,不定义重复 DTO。

namespace UserBaoTech.Foundation.Services
{
    public class KeyRequest
    {
        public string Id { get; set; }
    }
}

5.4 服务实现示例

多参数场景(Create/Update/List): 继承 ServiceBase<YourRequest, MessageResult<T>>

using System.Threading.Tasks;
using YourProject.Core.DTOs.Products;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.Core.Services.Products
{
    public class CreateProductService : ServiceBase<CreateProductRequest, MessageResult<string>>
    {
        private readonly IRepository<Product> _repository;

        public CreateProductService(
            IRepository<Product> repository,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
        }

        protected override async Task<MessageResult<string>> DoExecuteAsync(CreateProductRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.Name))
                return new MessageResult<string> { IsSuccess = false, Message = "产品名称不能为空", Code = 0 };

            var product = new Product
            {
                Name = request.Name.Trim(),
                Price = request.Price,
                Stock = request.Stock,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now,
                IsDeleted = false
            };

            var id = _repository.Insert(product);
            return new MessageResult<string> { IsSuccess = true, Data = id, Message = "创建成功" };
        }
    }
}

单参数场景(GetById/Delete): 继承 KeyServiceBase<MessageResult<T>>

using System.Threading.Tasks;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.Core.Services.Products
{
    public class GetProductByIdKeyService : KeyServiceBase<MessageResult<Product>>
    {
        private readonly IRepository<Product> _repository;

        public GetProductByIdKeyService(
            IRepository<Product> repository,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
        }

        protected override async Task<MessageResult<Product>> DoExecuteAsync(string id)
        {
            var product = _repository.GetById(id);
            if (product == null || product.IsDeleted)
                return new MessageResult<Product> { IsSuccess = false, Message = "产品不存在", Code = 0 };

            return new MessageResult<Product> { IsSuccess = true, Data = product };
        }
    }
}

第六部分:查询构建器(QueryBuilder)

6.1 核心方法

using System.Collections.Generic;
using UserBaoTech.Foundation.Data;

namespace UserBaoTech.Foundation.Utils
{
    public static class QueryBuilder
    {
        // 生成 SELECT 基础 SQL(不含分页)
        public static string BuildSelect(
            string tableName,
            IQueryParameters query,
            out IDictionary<string, object> parameters,
            out string orderBy);

        // 生成 COUNT SQL
        public static string BuildCount(
            string tableName,
            IQueryParameters query,
            out IDictionary<string, object> parameters);
    }
}

6.2 分页查询标准用法

// 在 Service 中使用
var tableName = _repository.GetTableName();
var baseSql = QueryBuilder.BuildSelect(tableName, query, out var parameters, out var orderBy);

// 分页索引转换:前端传 1 开始,底层期望 0 开始
var pageIndexForExecutor = query.PageIndex - 1;
if (pageIndexForExecutor < 0) pageIndexForExecutor = 0;

var pagedSql = _executor.GetPagedSql(baseSql, orderBy, pageIndexForExecutor, query.PageSize);
var items = _repository.Query(pagedSql, parameters);

var countSql = QueryBuilder.BuildCount(tableName, query, out var countParams);
var total = _executor.ExecuteScalar(countSql, countParams) ?? 0;

第七部分:分页索引转换规则(重要)

规则:前端传 1 开始,底层 DataAccessExecutor 期望 0 开始。

转换位置:在 Core 层的 Service 中完成,不动 Foundation。

// 正确做法:在 GetProductListService 中适配
var pageIndexForExecutor = query.PageIndex - 1;
if (pageIndexForExecutor < 0) pageIndexForExecutor = 0;
var pagedSql = _executor.GetPagedSql(baseSql, orderBy, pageIndexForExecutor, query.PageSize);

// 错误做法:修改 DataAccessExecutor(Foundation 层)
// Foundation 是只读基架,不能改

第八部分:统一返回格式(MessageResult)

8.1 结构定义

using System;
using System.Text.Json.Serialization;

namespace UserBaoTech.Foundation.Infrastructure
{
    public class MessageResult
    {
        [JsonPropertyName("success")]
        public bool IsSuccess { get; set; } = true;

        [JsonPropertyName("message")]
        public string Message { get; set; }

        [JsonPropertyName("code")]
        public int Code { get; set; } = 1;

        [JsonPropertyName("timestamp")]
        public DateTime Timestamp { get; set; } = DateTime.Now;
    }

    public class MessageResult<T> : MessageResult
    {
        [JsonPropertyName("data")]
        public T Data { get; set; }

        [JsonPropertyName("total")]
        public int RecordCount { get; set; }

        [JsonPropertyName("rows")]
        public T[] Rows { get; set; }
    }
}

8.2 使用规范

场景赋值方式
分页列表设置 Rows + RecordCountDatanull
单条/非分页设置 DataRowsnullRecordCount 为 0
增删改操作设置 Data(如新 ID 或 bool),RecordCount 为 1/0

第九部分:依赖注入注册

9.1 基础设施手动注册

// 在 Program.cs 中
builder.Services.AddSingleton<ILogger, NullLogger>();
builder.Services.AddSingleton<ICacheProvider, NullCacheProvider>();
builder.Services.AddSingleton<IConfigProvider, MemoryConfigProvider>();

var connectionString = builder.Configuration.GetConnectionString("Default");
builder.Services.AddScoped<IDbConnection>(sp => new MySqlConnection(connectionString));

builder.Services.AddScoped<IDataAccessExecutor>(sp =>
{
    var conn = sp.GetRequiredService<IDbConnection>();
    var logger = sp.GetRequiredService<ILogger>();
    return new DataAccessExecutor(conn, DatabaseType.MySql, logger);
});

builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();

9.2 自动注册(ServiceRegistrar 工具)

引用 UserBaoTech.Foundation.Tools.ServiceRegistrar,一行代码完成所有仓储和服务注册:

builder.Services.AddFoundationServices<Product>(ServiceScanningOptions.Development);

前提条件:仓储类以 Repository 结尾,服务类以 Service 结尾。

第十部分:新模块开发标准流程(5 步法)

假设要新增 Order(订单)模块:

步骤文件路径操作模板参考
1Core/Entities/Order.cs创建实体,实现 IEntity<string>Product 实体
2Core/Models/OrderQuery.cs创建查询模型,继承 QueryParametersBaseProductQuery
3Core/Data/OrderRepository.cs创建仓储,继承 RepositoryBase<Order>ProductRepository
4Core/Services/Orders/创建服务见下方清单
5WebAPI/Controllers/OrderController.cs创建控制器,注入服务见下方示例

步骤 4 服务清单:

服务继承基类用途
GetOrderListServiceServiceBase<GetOrderListRequest, MessageResult<Order>>分页列表
GetOrderByIdKeyServiceKeyServiceBase<MessageResult<Order>>按 ID 查询
CreateOrderServiceServiceBase<CreateOrderRequest, MessageResult<string>>新增
UpdateOrderServiceServiceBase<UpdateOrderRequest, MessageResult<bool>>更新
DeleteOrderKeyServiceKeyServiceBase<MessageResult<bool>>删除(主键)

Controller 标准写法:

using Microsoft.AspNetCore.Mvc;
using YourProject.Core.DTOs.Orders;
using YourProject.Core.Entities;
using YourProject.Core.Models;
using YourProject.Core.Services.Orders;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.WebAPI.Controllers;

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly GetOrderListService _listService;
    private readonly GetOrderByIdKeyService _getByIdService;
    private readonly CreateOrderService _createService;
    private readonly UpdateOrderService _updateService;
    private readonly DeleteOrderKeyService _deleteService;

    public OrderController(
        GetOrderListService listService,
        GetOrderByIdKeyService getByIdService,
        CreateOrderService createService,
        UpdateOrderService updateService,
        DeleteOrderKeyService deleteService)
    {
        _listService = listService;
        _getByIdService = getByIdService;
        _createService = createService;
        _updateService = updateService;
        _deleteService = deleteService;
    }

    [HttpGet]
    public async Task<MessageResult<Order>> GetList([FromQuery] OrderQuery query)
        => await _listService.ExecuteAsync(new GetOrderListRequest { Query = query });

    [HttpGet("{id}")]
    public async Task<MessageResult<Order>> GetById(string id)
        => await _getByIdService.ExecuteAsync(new KeyRequest { Id = id });

    [HttpPost]
    public async Task<MessageResult<string>> Create([FromBody] CreateOrderRequest request)
        => await _createService.ExecuteAsync(request);

    [HttpPut]
    public async Task<MessageResult<bool>> Update([FromBody] UpdateOrderRequest request)
        => await _updateService.ExecuteAsync(request);

    [HttpDelete("{id}")]
    public async Task<MessageResult<bool>> Delete(string id)
        => await _deleteService.ExecuteAsync(new KeyRequest { Id = id });
}

第十一部分:禁止事项(红线)

序号禁止事项正确做法
1❌ 在 Foundation 层添加业务实体或业务逻辑Foundation 只放纯技术抽象
2❌ 在业务层直接使用 IDbConnectionDataTable使用 IRepository<T> 接口
3❌ 在 Controller 中写业务逻辑所有业务逻辑在 Service 层
4❌ 定义仅含 Id 字段的 Request 类统一使用 KeyRequest
5❌ 修改 Foundation 层源码在 Core/WebAPI 层做适配
6❌ 在业务层编写 SQL 时硬编码数据库方言统一用 @参数名,由执行器自动适配
7❌ 在 Service 中直接 new 仓储对象通过构造函数依赖注入
8❌ 分页查询直接传 PageIndexGetPagedSql在 Service 层做 -1 转换

第十二部分:完整 Product 模块代码模板

见附录一(文档末尾)。

附录一:Product 模块完整代码

Product.cs(实体)

using System;
using UserBaoTech.Foundation.Entities;

namespace YourProject.Core.Entities
{
    public class Product : IEntity<string>
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
        public bool IsDeleted { get; set; }
    }
}

ProductQuery.cs(查询参数)

using System.Collections.Generic;
using UserBaoTech.Foundation.Data;

namespace YourProject.Core.Models
{
    public class ProductQuery : QueryParametersBase
    {
        public string Name { get; set; }
        public decimal? MinPrice { get; set; }
        public decimal? MaxPrice { get; set; }
        public bool? IsDeleted { get; set; }

        public override IDictionary<string, object> GetFilters()
        {
            var dict = new Dictionary<string, object>();
            if (!string.IsNullOrEmpty(Name))
                dict["Name LIKE"] = $"%{Name}%";
            if (MinPrice.HasValue)
                dict["Price >= "] = MinPrice.Value;
            if (MaxPrice.HasValue)
                dict["Price <= "] = MaxPrice.Value;
            if (IsDeleted.HasValue)
                dict["IsDeleted"] = IsDeleted.Value;
            return dict;
        }
    }
}

DTOs/Products/CreateProductRequest.cs

namespace YourProject.Core.DTOs.Products
{
    public class CreateProductRequest
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}

DTOs/Products/UpdateProductRequest.cs

namespace YourProject.Core.DTOs.Products
{
    public class UpdateProductRequest
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}

DTOs/Products/GetProductListRequest.cs

using YourProject.Core.Models;

namespace YourProject.Core.DTOs.Products
{
    public class GetProductListRequest
    {
        public ProductQuery Query { get; set; }
    }
}

ProductRepository.cs(仓储)

using System;
using System.Collections.Generic;
using System.Data;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;

namespace YourProject.Core.Data
{
    public class ProductRepository : RepositoryBase<Product>
    {
        public ProductRepository(IDataAccessExecutor executor) : base(executor) { }

        protected override string GetTableNameCore() => "Products";

        protected override Product MapToEntity(IDataReader reader)
        {
            return new Product
            {
                Id = reader["Id"].ToString(),
                Name = reader["Name"].ToString(),
                Price = Convert.ToDecimal(reader["Price"]),
                Stock = Convert.ToInt32(reader["Stock"]),
                CreatedAt = Convert.ToDateTime(reader["CreatedAt"]),
                UpdatedAt = Convert.ToDateTime(reader["UpdatedAt"]),
                IsDeleted = Convert.ToBoolean(reader["IsDeleted"])
            };
        }

        protected override IDictionary<string, object> GetInsertValues(Product entity)
        {
            return new Dictionary<string, object>
            {
                ["Id"] = entity.Id,
                ["Name"] = entity.Name,
                ["Price"] = entity.Price,
                ["Stock"] = entity.Stock,
                ["CreatedAt"] = DateTime.Now,
                ["UpdatedAt"] = DateTime.Now,
                ["IsDeleted"] = false
            };
        }

        protected override IDictionary<string, object> GetUpdateValues(Product entity)
        {
            return new Dictionary<string, object>
            {
                ["Name"] = entity.Name,
                ["Price"] = entity.Price,
                ["Stock"] = entity.Stock,
                ["UpdatedAt"] = DateTime.Now,
                ["IsDeleted"] = entity.IsDeleted
            };
        }

        public override int Delete(string id)
        {
            var sql = $"UPDATE {GetTableName()} SET IsDeleted = 1 WHERE Id = @Id";
            return Execute(sql, new Dictionary<string, object> { { "@Id", id } });
        }
    }
}

Services/Products/GetProductListService.cs

using System;
using System.Linq;
using System.Threading.Tasks;
using YourProject.Core.DTOs.Products;
using YourProject.Core.Entities;
using YourProject.Core.Models;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;
using UserBaoTech.Foundation.Utils;

namespace YourProject.Core.Services.Products
{
    public class GetProductListService : ServiceBase<GetProductListRequest, MessageResult<Product>>
    {
        private readonly IRepository<Product> _repository;
        private readonly IDataAccessExecutor _executor;

        public GetProductListService(
            IRepository<Product> repository,
            IDataAccessExecutor executor,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
            _executor = executor;
        }

        protected override async Task<MessageResult<Product>> DoExecuteAsync(GetProductListRequest request)
        {
            var query = request.Query ?? new ProductQuery();

            // 默认只显示未删除记录
            if (!query.IsDeleted.HasValue)
                query.IsDeleted = false;

            var tableName = _repository.GetTableName();
            var baseSql = QueryBuilder.BuildSelect(tableName, query, out var parameters, out var orderBy);

            // 分页索引转换:前端 1 开始 → 底层 0 开始
            var pageIndexForExecutor = query.PageIndex - 1;
            if (pageIndexForExecutor < 0) pageIndexForExecutor = 0;

            var pagedSql = _executor.GetPagedSql(baseSql, orderBy, pageIndexForExecutor, query.PageSize);
            var items = _repository.Query(pagedSql, parameters);

            var countSql = QueryBuilder.BuildCount(tableName, query, out var countParams);
            var total = _executor.ExecuteScalar(countSql, countParams) ?? 0;

            return new MessageResult<Product>
            {
                IsSuccess = true,
                Message = "查询成功",
                RecordCount = Convert.ToInt32(total),
                Rows = items.ToArray()
            };
        }
    }
}

Services/Products/CreateProductService.cs

using System;
using System.Threading.Tasks;
using YourProject.Core.DTOs.Products;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.Core.Services.Products
{
    public class CreateProductService : ServiceBase<CreateProductRequest, MessageResult<string>>
    {
        private readonly IRepository<Product> _repository;

        public CreateProductService(
            IRepository<Product> repository,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
        }

        protected override async Task<MessageResult<string>> DoExecuteAsync(CreateProductRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.Name))
                return new MessageResult<string> { IsSuccess = false, Message = "产品名称不能为空", Code = 0 };

            var product = new Product
            {
                Name = request.Name.Trim(),
                Price = request.Price,
                Stock = request.Stock,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now,
                IsDeleted = false
            };

            var id = _repository.Insert(product);
            return new MessageResult<string> { IsSuccess = true, Data = id, Message = "创建成功" };
        }
    }
}

Services/Products/UpdateProductService.cs

using System;
using System.Threading.Tasks;
using YourProject.Core.DTOs.Products;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.Core.Services.Products
{
    public class UpdateProductService : ServiceBase<UpdateProductRequest, MessageResult<bool>>
    {
        private readonly IRepository<Product> _repository;

        public UpdateProductService(
            IRepository<Product> repository,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
        }

        protected override async Task<MessageResult<bool>> DoExecuteAsync(UpdateProductRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.Id))
                return new MessageResult<bool> { IsSuccess = false, Message = "产品ID不能为空", Code = 0 };

            if (string.IsNullOrWhiteSpace(request.Name))
                return new MessageResult<bool> { IsSuccess = false, Message = "产品名称不能为空", Code = 0 };

            var existing = _repository.GetById(request.Id);
            if (existing == null || existing.IsDeleted)
                return new MessageResult<bool> { IsSuccess = false, Message = "产品不存在", Code = 0 };

            existing.Name = request.Name.Trim();
            existing.Price = request.Price;
            existing.Stock = request.Stock;
            existing.UpdatedAt = DateTime.Now;

            var rows = _repository.Update(existing);
            return new MessageResult<bool>
            {
                IsSuccess = rows > 0,
                Message = rows > 0 ? "更新成功" : "更新失败",
                Data = rows > 0
            };
        }
    }
}

Services/Products/GetProductByIdKeyService.cs

using System.Threading.Tasks;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.Core.Services.Products
{
    public class GetProductByIdKeyService : KeyServiceBase<MessageResult<Product>>
    {
        private readonly IRepository<Product> _repository;

        public GetProductByIdKeyService(
            IRepository<Product> repository,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
        }

        protected override async Task<MessageResult<Product>> DoExecuteAsync(string id)
        {
            var product = _repository.GetById(id);
            if (product == null || product.IsDeleted)
                return new MessageResult<Product> { IsSuccess = false, Message = "产品不存在", Code = 0 };

            return new MessageResult<Product> { IsSuccess = true, Data = product };
        }
    }
}

Services/Products/DeleteProductKeyService.cs

using System.Threading.Tasks;
using YourProject.Core.Entities;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.Core.Services.Products
{
    public class DeleteProductKeyService : KeyServiceBase<MessageResult<bool>>
    {
        private readonly IRepository<Product> _repository;

        public DeleteProductKeyService(
            IRepository<Product> repository,
            ILogger logger,
            ICacheProvider cache,
            IConfigProvider config) : base(logger, cache, config)
        {
            _repository = repository;
        }

        protected override async Task<MessageResult<bool>> DoExecuteAsync(string id)
        {
            return await DeleteAsync(_repository, id);
        }

        protected override (bool success, string message) CanDeleteWithMessage(IEntity<string> entity)
        {
            if (entity == null)
                return (false, "产品不存在");

            if (entity.IsDeleted)
                return (false, "产品已被删除");

            // 示例:系统数据保护
            if (int.TryParse(entity.Id, out int id) && id < 10)
                return (false, "系统数据不可删除");

            return (true, null);
        }

        protected override (bool success, string message) OnDeletedWithMessage(IEntity<string> entity)
        {
            var product = entity as Product;
            return (true, product != null ? $"产品 {product.Name} 已成功删除" : "删除成功");
        }
    }
}

ProductController.cs(控制器)

using Microsoft.AspNetCore.Mvc;
using YourProject.Core.DTOs.Products;
using YourProject.Core.Entities;
using YourProject.Core.Models;
using YourProject.Core.Services.Products;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Services;

namespace YourProject.WebAPI.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly GetProductListService _listService;
    private readonly GetProductByIdKeyService _getByIdService;
    private readonly CreateProductService _createService;
    private readonly UpdateProductService _updateService;
    private readonly DeleteProductKeyService _deleteService;

    public ProductController(
        GetProductListService listService,
        GetProductByIdKeyService getByIdService,
        CreateProductService createService,
        UpdateProductService updateService,
        DeleteProductKeyService deleteService)
    {
        _listService = listService;
        _getByIdService = getByIdService;
        _createService = createService;
        _updateService = updateService;
        _deleteService = deleteService;
    }

    [HttpGet]
    public async Task<MessageResult<Product>> GetList([FromQuery] ProductQuery query)
        => await _listService.ExecuteAsync(new GetProductListRequest { Query = query });

    [HttpGet("{id}")]
    public async Task<MessageResult<Product>> GetById(string id)
        => await _getByIdService.ExecuteAsync(new KeyRequest { Id = id });

    [HttpPost]
    public async Task<MessageResult<string>> Create([FromBody] CreateProductRequest request)
        => await _createService.ExecuteAsync(request);

    [HttpPut]
    public async Task<MessageResult<bool>> Update([FromBody] UpdateProductRequest request)
        => await _updateService.ExecuteAsync(request);

    [HttpDelete("{id}")]
    public async Task<MessageResult<bool>> Delete(string id)
        => await _deleteService.ExecuteAsync(new KeyRequest { Id = id });
}

Program.cs(完整启动配置)

using YourProject.Core.Data;
using YourProject.Core.Entities;
using YourProject.Core.Services.Products;
using MySqlConnector;
using System.Data;
using UserBaoTech.Foundation.Data;
using UserBaoTech.Foundation.Infrastructure;
using UserBaoTech.Foundation.Tools.ServiceRegistrar;

// 别名解决命名冲突
using FoundationLogger = UserBaoTech.Foundation.Infrastructure.ILogger;
using FoundationNullLogger = UserBaoTech.Foundation.Infrastructure.NullLogger;

var builder = WebApplication.CreateBuilder(args);

// 1. 数据库连接
var connectionString = builder.Configuration.GetConnectionString("Default");
builder.Services.AddScoped<IDbConnection>(sp => new MySqlConnection(connectionString));

// 2. 数据访问执行器
builder.Services.AddScoped<IDataAccessExecutor>(sp =>
{
    var conn = sp.GetRequiredService<IDbConnection>();
    var logger = sp.GetRequiredService<FoundationLogger>();
    return new DataAccessExecutor(conn, DatabaseType.MySql, logger);
});

// 3. 基础设施
builder.Services.AddSingleton<FoundationLogger, FoundationNullLogger>();
builder.Services.AddSingleton<ICacheProvider, NullCacheProvider>();
builder.Services.AddSingleton<IConfigProvider, MemoryConfigProvider>();

// 4. 工作单元
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();

// 5. 自动注册仓储和服务
builder.Services.AddFoundationServices<Product>(ServiceScanningOptions.Development);

// 6. 控制器
builder.Services.AddControllers();

// 7. CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", policy =>
        policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});

// 8. 构建应用
var app = builder.Build();

app.UseStaticFiles();
app.UseCors("AllowAll");
app.MapControllers();

app.Run();

appsettings.json

{
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=yourdb;Uid=root;Pwd=yourpassword;CharSet=utf8mb4;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

launchSettings.json(参考配置)

{
  "profiles": {
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "applicationUrl": "https://localhost:7222;http://localhost:5196",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

文档结束

本份文档是唯一的、最终的、完整的架构开发指南。

  • 小白开发人员:按文档从零创建项目、复制模板、修改字段,即可完成业务模块开发。
  • AI 新对话框:读取本份文档后,即可理解全部设计逻辑、分层边界、代码规范,独立完成符合架构要求的新模块生成。
  • 无需任何其他历史文档、聊天记录、源码文件作为补充。

版本:v3.0(最终版)
发布日期:2026年7月11日
作者:周方勇/咏方舟-长江支流(金质打印通、用宝框架开源作者)
适用范围:所有基于 UserBaoTech.Foundation 的新项目开发

反馈与交流:欢迎转载,不限拷贝,欢迎在评论区留言或私信交流。如果你正在寻找一套 不用 EF、不用 Dapper、可跨语言迁移 的轻量级基架,用宝框架或许是一个值得尝试的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值