根据你的需求,我将为你提供一个基于 SQL Server 数据库的代码生成工具实现方案,针对 tb_warehouse_area 表生成符合 DDD 和 ABP 框架的 CRUD 功能代码,重点实现以下功能:
自定义 T4 模板:允许用户提供自定义 T4 模板以增加灵活性。
增量生成:仅生成新增或修改表的代码,避免覆盖现有代码。
集成测试:生成单元测试代码以验证 CRUD 功能。
类型映射:处理 tinyint 映射为枚举,float 映射为 double,并支持可配置的类型映射。
以下是详细的实现方案,基于提供的 tb_warehouse_area 表结构,包含中文解释。
1. 需求分析
1.1 表结构
tb_warehouse_area 表结构如下:

列名

数据类型

描述

id

bigint

主键

name

varchar

名称

number

varchar

编号

warehouse_id

bigint

仓库 ID

type

tinyint

类型(映射为枚举)

max_temperature

float

最大温度(映射为 double)

min_temperature

float

最小温度(映射为 double)

max_humidity

float

最大湿度(映射为 double)

min_humidity

float

最小湿度(映射为 double)

disabled_id

bigint

禁用状态 ID

creator_id

bigint

创建者 ID

create_date

datetime

创建时间

last_modifier_id

bigint

最后修改者 ID

last_modify_date

datetime

最后修改时间

tenant_id

bigint

租户 ID

factory_id

bigint

工厂 ID

1.2 功能要求
DDD 和 ABP 架构:生成 Domain 层(实体、枚举、仓储接口)、Application 层(DTO、应用服务)、Infrastructure 层(EF Core 配置、仓储实现)、API 层(控制器)。
类型映射:
tinyint(如 type)映射为 C# 枚举(如 WarehouseAreaTypeEnum)。
float(如 max_temperature 等)映射为 C# double。
其他类型(如 varchar、bigint、datetime)按标准映射(string、long、DateTime)。
自定义 T4 模板:用户可提供 T4 模板文件,工具根据模板生成代码。
增量生成:检查目标项目目录,仅生成新增或修改表的代码,保留用户手动修改的内容。
集成测试:为每个实体生成单元测试代码,验证 CRUD 操作。
配置文件:使用 JSON 文件配置类型映射,支持用户自定义。
2. 工具设计
2.1 核心组件
TableSchemaReader:从 SQL Server 数据库读取表结构。
TypeMapper:根据配置文件处理类型映射(如 tinyint → 枚举,float → double)。
TemplateEngine:基于 T4 模板生成代码,支持用户提供的自定义模板。
IncrementalGenerator:实现增量生成逻辑,检查现有文件并决定是否生成。
TestGenerator:生成单元测试代码,验证 CRUD 功能。
ConfigurationManager:管理类型映射配置文件。
2.2 类型映射配置
创建 TypeMapping.json 文件,定义数据库类型到 C# 类型的映射:
json
{
  "TypeMappings": [
    {
      "DatabaseType": "tinyint",
      "CSharpType": "enum",
      "DefaultEnumName": "{ColumnName}Enum",
      "EnumValues": ["Unknown", "Active", "Inactive"]
    },
    {
      "DatabaseType": "float",
      "CSharpType": "double"
    },
    {
      "DatabaseType": "varchar",
      "CSharpType": "string"
    },
    {
      "DatabaseType": "bigint",
      "CSharpType": "long"
    },
    {
      "DatabaseType": "datetime",
      "CSharpType": "DateTime"
    }
  ]
}
tinyint 映射为枚举,枚举名称基于列名(如 type → WarehouseAreaTypeEnum)。
float 映射为 double。
用户可通过修改 TypeMapping.json 添加或调整映射规则。
2.3 技术选型
语言:C# (.NET Core 8.x)。
框架:ABP vNext(支持 DDD)。
数据库:SQL Server,EF Core 作为 ORM。
模板引擎:T4 模板(支持用户自定义)。
测试框架:xUnit 或 NUnit,用于生成单元测试。
文件操作:System.IO 和 Roslyn(用于分析现有代码)。
3. 实现步骤
3.1 表结构解析
使用 SQL Server 的 INFORMATION_SCHEMA 读取 tb_warehouse_area 表结构。
csharp
public class TableSchemaReader
{
    private readonly string _connectionString;

    public TableSchemaReader(string connectionString)
    {
        _connectionString = connectionString;
    }

    public List<TableSchema> GetTableSchemas()
    {
        var tables = new List<TableSchema>();
        using var connection = new SqlConnection(_connectionString);
        connection.Open();

        var sql = @"SELECT 
                        t.TABLE_NAME, 
                        c.COLUMN_NAME, 
                        c.DATA_TYPE, 
                        CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey
                    FROM INFORMATION_SCHEMA.TABLES t
                    JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME
                    LEFT JOIN (
                        SELECT ku.TABLE_NAME, ku.COLUMN_NAME
                        FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
                        JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku 
                            ON tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
                        WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
                    ) pk ON c.TABLE_NAME = pk.TABLE_NAME AND c.COLUMN_NAME = pk.COLUMN_NAME
                    WHERE t.TABLE_TYPE = 'BASE TABLE'";

        using var command = new SqlCommand(sql, connection);
        using var reader = command.ExecuteReader();

        var tableDict = new Dictionary<string, TableSchema>();
        while (reader.Read())
        {
            var tableName = reader["TABLE_NAME"].ToString();
            if (!tableDict.ContainsKey(tableName))
            {
                tableDict[tableName] = new TableSchema { TableName = tableName, Columns = new List<ColumnSchema>() };
            }

            tableDict[tableName].Columns.Add(new ColumnSchema
            {
                ColumnName = reader["COLUMN_NAME"].ToString(),
                DataType = reader["DATA_TYPE"].ToString(),
                IsPrimaryKey = Convert.ToBoolean(reader["IsPrimaryKey"])
            });
        }

        return tableDict.Values.ToList();
    }
}

public class TableSchema
{
    public string TableName { get; set; }
    public List<ColumnSchema> Columns { get; set; }
}

public class ColumnSchema
{
    public string ColumnName { get; set; }
    public string DataType { get; set; }
    public bool IsPrimaryKey { get; set; }
}
3.2 类型映射
实现类型映射器,读取 TypeMapping.json 并将数据库类型转换为 C# 类型。
csharp
public class TypeMapper
{
    private readonly Dictionary<string, TypeMapping> _mappings;

    public TypeMapper(string configFilePath)
    {
        var config = JsonSerializer.Deserialize<TypeMappingConfig>(File.ReadAllText(configFilePath));
        _mappings = config.TypeMappings.ToDictionary(m => m.DatabaseType.ToLower(), m => m);
    }

    public string MapToCSharpType(string dbType, string columnName)
    {
        if (_mappings.TryGetValue(dbType.ToLower(), out var mapping))
        {
            if (mapping.CSharpType == "enum")
            {
                return mapping.DefaultEnumName.Replace("{ColumnName}", ToPascalCase(columnName));
            }
            return mapping.CSharpType;
        }
        return "string"; // 默认映射
    }

    public List<string> GetEnumValues(string dbType)
    {
        return _mappings.TryGetValue(dbType.ToLower(), out var mapping) ? mapping.EnumValues : null;
    }

    private string ToPascalCase(string input)
    {
        return string.Concat(input.Split('_').Select(s => char.ToUpper(s[0]) + s.Substring(1).ToLower()));
    }
}

public class TypeMappingConfig
{
    public List<TypeMapping> TypeMappings { get; set; }
}

public class TypeMapping
{
    public string DatabaseType { get; set; }
    public string CSharpType { get; set; }
    public string DefaultEnumName { get; set; }
    public List<string> EnumValues { get; set; }
}
3.3 自定义 T4 模板
用户可以提供自定义 T4 模板,工具通过 TextTemplatingEngine 执行模板。以下是默认的 T4 模板示例(用户可覆盖)。
3.3.1 实体模板(Entity.tt)
tt
<#@ template language="C#" #>
<#@ parameter name="Table" type="TableSchema" #>
<#@ parameter name="TypeMapper" type="TypeMapper" #>
namespace MyApp.Domain.Entities
{
    public class <#= Table.TableName.ToPascalCase() #> : AuditedAggregateRoot<long>
    {
        <# foreach (var column in Table.Columns) { #>
        public <#= TypeMapper.MapToCSharpType(column.DataType, column.ColumnName) #> <#= column.ColumnName.ToPascalCase() #> { get; set; }
        <# } #>
    }

    <# foreach (var column in Table.Columns.Where(c => TypeMapper.MapToCSharpType(c.DataType, c.ColumnName).EndsWith("Enum"))) { #>
    public enum <#= TypeMapper.MapToCSharpType(column.DataType, column.ColumnName) #>
    {
        <# var enumValues = TypeMapper.GetEnumValues(column.DataType); #>
        <# foreach (var value in enumValues) { #>
        <#= value #> = <#= Array.IndexOf(enumValues, value) #>,
        <# } #>
    }
    <# } #>
}

<#+
public static class Extensions
{
    public static string ToPascalCase(this string input)
    {
        return string.Concat(input.Split('_').Select(s => char.ToUpper(s[0]) + s.Substring(1).ToLower()));
    }
}
#>
用户可提供自己的 Entity.tt 文件,工具会优先使用用户模板。
3.3.2 其他模板
类似地,为仓储接口、DTO、应用服务、EF 配置和控制器创建默认 T4 模板,用户可覆盖。
3.4 增量生成
实现增量生成逻辑,检查目标目录中是否已有文件,并比较表结构是否变化。
csharp
public class IncrementalGenerator
{
    private readonly string _outputPath;

    public IncrementalGenerator(string outputPath)
    {
        _outputPath = outputPath;
    }

    public bool ShouldGenerate(TableSchema table, string fileType)
    {
        var filePath = GetFilePath(table, fileType);
        if (!File.Exists(filePath))
        {
            return true; // 文件不存在,需生成
        }

        // 读取现有文件,解析内容(使用 Roslyn 分析代码结构)
        // 比较表结构(列名、类型等)是否变化
        // 简单实现:假设表结构变化时强制重新生成
        return true; // 实际需实现更复杂的比较逻辑
    }

    private string GetFilePath(TableSchema table, string fileType)
    {
        return fileType switch
        {
            "Entity" => Path.Combine(_outputPath, "Domain", "Entities", $"{table.TableName.ToPascalCase()}.cs"),
            "Repository" => Path.Combine(_outputPath, "Domain", "Repositories", $"I{table.TableName.ToPascalCase()}Repository.cs"),
            // 其他文件路径...
            _ => throw new ArgumentException("Invalid file type")
        };
    }
}
3.5 集成测试生成
为 tb_warehouse_area 表生成单元测试代码,使用 xUnit 框架。
3.5.1 测试模板(UnitTest.tt)
tt
<#@ template language="C#" #>
<#@ parameter name="Table" type="TableSchema" #>
using MyApp.Application;
using MyApp.Domain.Entities;
using Volo.Abp.Testing;
using Xunit;

namespace MyApp.Tests
{
    public class <#= Table.TableName.ToPascalCase() #>AppServiceTests : AbpIntegratedTest<MyAppTestModule>
    {
        private readonly I<#= Table.TableName.ToPascalCase() #>AppService _appService;

        public <#= Table.TableName.ToPascalCase() #>AppServiceTests()
        {
            _appService = GetRequiredService<I<#= Table.TableName.ToPascalCase() #>AppService>();
        }

        [Fact]
        public async Task Should_Create_And_Get_<#= Table.TableName.ToPascalCase() #>()
        {
            // Arrange
            var input = new CreateUpdate<#= Table.TableName.ToPascalCase() #>Dto
            {
                Name = "TestArea",
                Number = "A001",
                WarehouseId = 1,
                Type = WarehouseAreaTypeEnum.Active,
                MaxTemperature = 25.5,
                MinTemperature = 15.5,
                MaxHumidity = 70.0,
                MinHumidity = 30.0,
                DisabledId = 0,
                CreatorId = 1,
                CreateDate = DateTime.Now,
                TenantId = 1,
                FactoryId = 1
            };

            // Act
            var created = await _appService.CreateAsync(input);
            var retrieved = await _appService.GetAsync(created.Id);

            // Assert
            Assert.Equal(input.Name, retrieved.Name);
            Assert.Equal(input.Number, retrieved.Number);
            Assert.Equal(input.Type, retrieved.Type);
        }

        // 其他测试方法(Update、Delete、GetList)...
    }
}
3.6 生成的代码示例
基于 tb_warehouse_area 表,以下是生成的代码片段(假设表名转换为 PascalCase 的 WarehouseArea)。
3.6.1 Domain 层 - 实体
csharp
namespace MyApp.Domain.Entities
{
    public class WarehouseArea : AuditedAggregateRoot<long>
    {
        public string Name { get; set; }
        public string Number { get; set; }
        public long WarehouseId { get; set; }
        public WarehouseAreaTypeEnum Type { get; set; }
        public double MaxTemperature { get; set; }
        public double MinTemperature { get; set; }
        public double MaxHumidity { get; set; }
        public double MinHumidity { get; set; }
        public long DisabledId { get; set; }
        public long CreatorId { get; set; }
        public DateTime CreateDate { get; set; }
        public long LastModifierId { get; set; }
        public DateTime LastModifyDate { get; set; }
        public long TenantId { get; set; }
        public long FactoryId { get; set; }
    }

    public enum WarehouseAreaTypeEnum
    {
        Unknown = 0,
        Active = 1,
        Inactive = 2
    }
}
3.6.2 Domain 层 - 仓储接口
csharp
public interface IWarehouseAreaRepository : IRepository<WarehouseArea, long>
{
    Task<List<WarehouseArea>> GetByTypeAsync(WarehouseAreaTypeEnum type);
}
3.6.3 Application 层 - DTO
csharp
public class WarehouseAreaDto : AuditedEntityDto<long>
{
    public string Name { get; set; }
    public string Number { get; set; }
    public long WarehouseId { get; set; }
    public WarehouseAreaTypeEnum Type { get; set; }
    public double MaxTemperature { get; set; }
    public double MinTemperature { get; set; }
    public double MaxHumidity { get; set; }
    public double MinHumidity { get; set; }
    public long DisabledId { get; set; }
    public long CreatorId { get; set; }
    public DateTime CreateDate { get; set; }
    public long LastModifierId { get; set; }
    public DateTime LastModifyDate { get; set; }
    public long TenantId { get; set; }
    public long FactoryId { get; set; }
}

public class CreateUpdateWarehouseAreaDto
{
    public string Name { get; set; }
    public string Number { get; set; }
    public long WarehouseId { get; set; }
    public WarehouseAreaTypeEnum Type { get; set; }
    public double MaxTemperature { get; set; }
    public double MinTemperature { get; set; }
    public double MaxHumidity { get; set; }
    public double MinHumidity { get; set; }
    public long DisabledId { get; set; }
    public long TenantId { get; set; }
    public long FactoryId { get; set; }
}
3.6.4 Application 层 - 应用服务
csharp
public interface IWarehouseAreaAppService : IApplicationService
{
    Task<WarehouseAreaDto> GetAsync(long id);
    Task<PagedResultDto<WarehouseAreaDto>> GetListAsync(PagedAndSortedResultRequestDto input);
    Task<WarehouseAreaDto> CreateAsync(CreateUpdateWarehouseAreaDto input);
    Task<WarehouseAreaDto> UpdateAsync(long id, CreateUpdateWarehouseAreaDto input);
    Task DeleteAsync(long id);
}

public class WarehouseAreaAppService : ApplicationService, IWarehouseAreaAppService
{
    private readonly IWarehouseAreaRepository _warehouseAreaRepository;

    public WarehouseAreaAppService(IWarehouseAreaRepository warehouseAreaRepository)
    {
        _warehouseAreaRepository = warehouseAreaRepository;
    }

    public async Task<WarehouseAreaDto> GetAsync(long id)
    {
        var entity = await _warehouseAreaRepository.GetAsync(id);
        return ObjectMapper.Map<WarehouseArea, WarehouseAreaDto>(entity);
    }

    // 其他 CRUD 方法...
}
3.6.5 Infrastructure 层 - EF Core 配置
csharp
public class WarehouseAreaConfiguration : IEntityTypeConfiguration<WarehouseArea>
{
    public void Configure(EntityTypeBuilder<WarehouseArea> builder)
    {
        builder.ToTable("tb_warehouse_area");
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).HasMaxLength(255).IsRequired();
        builder.Property(x => x.Number).HasMaxLength(255).IsRequired();
        builder.Property(x => x.Type).HasConversion<int>();
        builder.Property(x => x.MaxTemperature).HasColumnType("float");
        builder.Property(x => x.MinTemperature).HasColumnType("float");
        builder.Property(x => x.MaxHumidity).HasColumnType("float");
        builder.Property(x => x.MinHumidity).HasColumnType("float");
    }
}
3.6.6 API 层 - 控制器
csharp
[Route("api/warehouse-areas")]
public class WarehouseAreaController : AbpControllerBase
{
    private readonly IWarehouseAreaAppService _appService;

    public WarehouseAreaController(IWarehouseAreaAppService appService)
    {
        _appService = appService;
    }

    [HttpGet("{id}")]
    public async Task<WarehouseAreaDto> GetAsync(long id)
    {
        return await _appService.GetAsync(id);
    }

    [HttpPost]
    public async Task<WarehouseAreaDto> CreateAsync(CreateUpdateWarehouseAreaDto input)
    {
        return await _appService.CreateAsync(input);
    }

    // 其他 CRUD 方法...
}
3.7 工具主程序
csharp
public class CodeGenerator
{
    private readonly TableSchemaReader _schemaReader;
    private readonly TypeMapper _typeMapper;
    private readonly IncrementalGenerator _incrementalGenerator;
    private readonly string _outputPath;
    private readonly string _customTemplatePath;

    public CodeGenerator(string connectionString, string configFilePath, string outputPath, string customTemplatePath)
    {
        _schemaReader = new TableSchemaReader(connectionString);
        _typeMapper = new TypeMapper(configFilePath);
        _incrementalGenerator = new IncrementalGenerator(outputPath);
        _outputPath = outputPath;
        _customTemplatePath = customTemplatePath;
    }

    public void Generate()
    {
        var tables = _schemaReader.GetTableSchemas();
        foreach (var table in tables)
        {
            if (_incrementalGenerator.ShouldGenerate(table, "Entity"))
            {
                GenerateFile(table, "Entity", Path.Combine(_customTemplatePath, "Entity.tt") ?? "DefaultEntity.tt");
            }
            if (_incrementalGenerator.ShouldGenerate(table, "Repository"))
            {
                GenerateFile(table, "Repository", Path.Combine(_customTemplatePath, "Repository.tt") ?? "DefaultRepository.tt");
            }
            if (_incrementalGenerator.ShouldGenerate(table, "AppService"))
            {
                GenerateFile(table, "AppService", Path.Combine(_customTemplatePath, "AppService.tt") ?? "DefaultAppService.tt");
            }
            if (_incrementalGenerator.ShouldGenerate(table, "Dto"))
            {
                GenerateFile(table, "Dto", Path.Combine(_customTemplatePath, "Dto.tt") ?? "DefaultDto.tt");
            }
            if (_incrementalGenerator.ShouldGenerate(table, "EfConfiguration"))
            {
                GenerateFile(table, "EfConfiguration", Path.Combine(_customTemplatePath, "EfConfiguration.tt") ?? "DefaultEfConfiguration.tt");
            }
            if (_incrementalGenerator.ShouldGenerate(table, "Controller"))
            {
                GenerateFile(table, "Controller", Path.Combine(_customTemplatePath, "Controller.tt") ?? "DefaultController.tt");
            }
            if (_incrementalGenerator.ShouldGenerate(table, "UnitTest"))
            {
                GenerateFile(table, "UnitTest", Path.Combine(_customTemplatePath, "UnitTest.tt") ?? "DefaultUnitTest.tt");
            }
        }
    }

    private void GenerateFile(TableSchema table, string fileType, string templatePath)
    {
        var template = new TemplateEngine(templatePath, _typeMapper);
        var code = template.TransformText(table);
        var filePath = _incrementalGenerator.GetFilePath(table, fileType);
        Directory.CreateDirectory(Path.GetDirectoryName(filePath));
        File.WriteAllText(filePath, code);
    }
}
3.8 模板引擎
实现简单的 T4 模板引擎(或使用 Microsoft.T4.TextTemplating)。
csharp
public class TemplateEngine
{
    private readonly string _templatePath;
    private readonly TypeMapper _typeMapper;

    public TemplateEngine(string templatePath, TypeMapper typeMapper)
    {
        _templatePath = templatePath;
        _typeMapper = typeMapper;
    }

    public string TransformText(TableSchema table)
    {
        // 加载 T4 模板并执行
        // 实际实现需依赖 T4 引擎(如 Microsoft.T4.TextTemplating)
        // 这里简化为读取模板并替换占位符
        var templateContent = File.ReadAllText(_templatePath);
        // 使用 T4 引擎渲染模板,传入 table 和 typeMapper
        // 返回渲染后的代码
        return templateContent; // 需替换为实际 T4 渲染逻辑
    }
}
4. 使用说明
准备配置文件:
创建 TypeMapping.json,定义类型映射(如上所示)。
可选:提供自定义 T4 模板文件(如 Entity.tt、UnitTest.tt)。
运行工具:
bash
dotnet run --connectionString "Server=localhost;Database=MyDb;Trusted_Connection=True;" --configFile "TypeMapping.json" --outputPath "MyApp" --customTemplatePath "Templates"
整合到 ABP 项目:
将生成的代码放入 ABP 项目的对应模块(如 MyApp.Domain、MyApp.Application 等)。
在 DbContext 中添加实体配置:
csharp
modelBuilder.ApplyConfiguration(new WarehouseAreaConfiguration());
运行 EF Core 迁移:
bash
dotnet ef migrations add InitialCreate
dotnet ef database update
运行单元测试:
确保测试项目引用 MyApp.Tests 模块。
运行测试:
bash
dotnet test
5. 注意事项
自定义模板:
用户需确保自定义 T4 模板与默认模板的参数兼容(如 Table 和 TypeMapper)。
可提供工具内置的默认模板,方便用户修改。
增量生成:
当前实现假设表结构变化时重新生成,实际项目中可使用 Roslyn 分析现有代码的 AST,精确比较表结构变化。
可添加时间戳或版本号到生成的文件,记录生成时间。
单元测试:
测试代码依赖 ABP 的 AbpIntegratedTest 基类,确保测试环境配置正确(如 MyAppTestModule)。
测试数据需根据实际业务场景调整(如 Type 枚举值的选择)。
性能优化:
缓存表结构信息,避免重复查询数据库。
对大型项目,考虑并行生成代码以提高效率。
错误处理:
验证数据库连接字符串、配置文件和模板文件的有效性。
提供详细的错误日志,方便用户排查问题。
ABP 版本:
确保生成代码与目标 ABP 版本(例如 vNext 8.x)兼容,注意 ABP 的 AuditedAggregateRoot 和 ObjectMapper 使用。
6. 扩展功能
多数据库支持:扩展 TableSchemaReader 支持 MySQL、PostgreSQL 等其他数据库。
模板验证:在生成代码前验证用户提供的 T4 模板是否合法。
代码格式化:集成 CSharpier 或 Roslyn 格式化生成的代码,确保一致性。
多语言支持:为 DTO 和控制器生成多语言支持(如 ABP 的 IStringLocalizer)。
API 文档:为控制器生成 Swagger 注解,方便 API 文档生成。
7. 总结
该工具针对 tb_warehouse_area 表生成符合 DDD 和 ABP 框架的 CRUD 代码,支持:
tinyint 映射为枚举(如 WarehouseAreaTypeEnum),float 映射为 double。
用户自定义 T4 模板,增加灵活性。
增量生成,避免覆盖现有代码。
集成 xUnit 单元测试,验证 CRUD 功能。
生成的代码包括实体、仓储、DTO、应用服务、EF 配置、控制器和单元测试,完整适配 ABP 框架。如果需要更详细的某部分代码(如具体的 T4 模板实现或 Roslyn 增量生成逻辑),请告诉我!

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐