Dapper如何实现代码优先的思路 Dapper与数据库迁移

来源:站长工具作者:樱由罗头衔:网络博主
导读:本期聚焦于小伙伴创作的《Dapper如何实现代码优先的思路 Dapper与数据库迁移》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Dapper如何实现代码优先的思路 Dapper与数据库迁移》有用,将其分享出去将是对创作者最好的鼓励。

Dapper是一款轻量级的.NET ORM框架,本身只提供了对象与数据库结果集的映射能力,没有像Entity Framework那样内置代码优先和数据库迁移的功能。但我们可以通过自定义逻辑或者结合轻量工具,实现基于实体类自动生成数据库表、管理数据库结构变更的需求。

Dapper如何实现代码优先的思路 Dapper与数据库迁移

Dapper代码优先的核心实现思路

代码优先的核心逻辑是:先定义业务实体类,再通过程序读取实体类的结构信息(属性名称、类型、特性标注等),自动生成对应的数据库表创建语句并执行。实现这个流程主要分为三步:实体结构解析、SQL语句生成、数据库执行。

1. 实体结构解析

我们可以通过C#的反射功能,读取实体类的属性和标注的特性,获取每个属性对应的数据库字段名、数据类型、约束信息。比如我们可以自定义一个TableAttribute特性标注表名,用ColumnAttribute标注字段信息。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

// 自定义表特性,标注实体对应的表名
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
    public string Name { get; set; }
    public TableAttribute(string name)
    {
        Name = name;
    }
}

// 自定义字段特性,标注字段信息
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
    public string Name { get; set; }
    public string DbType { get; set; }
    public bool IsPrimaryKey { get; set; }
    public bool IsNullable { get; set; }
    public bool IsAutoIncrement { get; set; }

    public ColumnAttribute(string name, string dbType, bool isPrimaryKey = false, bool isNullable = true, bool isAutoIncrement = false)
    {
        Name = name;
        DbType = dbType;
        IsPrimaryKey = isPrimaryKey;
        IsNullable = isNullable;
        IsAutoIncrement = isAutoIncrement;
    }
}

// 示例实体类
[Table("user_info")]
public class User
{
    [Column("id", "int", true, false, true)]
    public int Id { get; set; }

    [Column("user_name", "varchar(50)", false, false)]
    public string UserName { get; set; }

    [Column("age", "int", false, true)]
    public int? Age { get; set; }

    [Column("create_time", "datetime", false, false)]
    public DateTime CreateTime { get; set; }
}

// 实体结构解析工具类
public class EntityParser
{
    // 获取实体对应的表名
    public static string GetTableName<T>() where T : class
    {
        var tableAttr = typeof(T).GetCustomAttribute<TableAttribute>();
        if (tableAttr != null)
        {
            return tableAttr.Name;
        }
        return typeof(T).Name;
    }

    // 获取实体所有字段信息
    public static List<ColumnInfo> GetColumnInfos<T>() where T : class
    {
        var columns = new List<ColumnInfo>();
        var properties = typeof(T).GetProperties();
        foreach (var prop in properties)
        {
            var columnAttr = prop.GetCustomAttribute<ColumnAttribute>();
            if (columnAttr != null)
            {
                columns.Add(new ColumnInfo
                {
                    PropertyName = prop.Name,
                    ColumnName = columnAttr.Name,
                    DbType = columnAttr.DbType,
                    IsPrimaryKey = columnAttr.IsPrimaryKey,
                    IsNullable = columnAttr.IsNullable,
                    IsAutoIncrement = columnAttr.IsAutoIncrement
                });
            }
        }
        return columns;
    }
}

// 字段信息模型
public class ColumnInfo
{
    public string PropertyName { get; set; }
    public string ColumnName { get; set; }
    public string DbType { get; set; }
    public bool IsPrimaryKey { get; set; }
    public bool IsNullable { get; set; }
    public bool IsAutoIncrement { get; set; }
}

2. SQL建表语句生成

解析完实体结构后,需要把字段信息拼接成符合数据库语法的建表SQL。这里以SQL Server为例,生成创建表的语句,同时处理主键、自增、非空等约束。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class SqlGenerator
{
    // 生成创建表的SQL语句
    public static string GenerateCreateTableSql<T>() where T : class
    {
        string tableName = EntityParser.GetTableName<T>();
        var columns = EntityParser.GetColumnInfos<T>();
        if (columns.Count == 0)
        {
            throw new Exception("实体类没有标注字段信息");
        }

        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.AppendLine($"IF NOT EXISTS (SELECT * FROM sysobjects WHERE name = '{tableName}' AND xtype = 'U')");
        sqlBuilder.AppendLine($"BEGIN");
        sqlBuilder.AppendLine($"    CREATE TABLE {tableName} (");

        List<string> columnSqlList = new List<string>();
        List<string> primaryKeyColumns = new List<string>();

        foreach (var column in columns)
        {
            StringBuilder columnSql = new StringBuilder();
            columnSql.Append($"        {column.ColumnName} {column.DbType}");

            if (!column.IsNullable)
            {
                columnSql.Append(" NOT NULL");
            }

            if (column.IsAutoIncrement)
            {
                columnSql.Append(" IDENTITY(1,1)");
            }

            columnSqlList.Add(columnSql.ToString());

            if (column.IsPrimaryKey)
            {
                primaryKeyColumns.Add(column.ColumnName);
            }
        }

        sqlBuilder.AppendLine(string.Join(",n", columnSqlList));

        // 添加主键约束
        if (primaryKeyColumns.Count > 0)
        {
            sqlBuilder.AppendLine($"        , PRIMARY KEY ({string.Join(", ", primaryKeyColumns)})");
        }

        sqlBuilder.AppendLine("    )");
        sqlBuilder.AppendLine("END");

        return sqlBuilder.ToString();
    }
}

3. 结合Dapper执行建表语句

生成SQL之后,使用Dapper的Execute方法执行语句,完成表的创建。需要注意的是,执行前要确认数据库连接已经打开。

using System.Data.SqlClient;
using Dapper;

public class DbInitializer
{
    private readonly string _connectionString;

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

    // 初始化表结构
    public void InitializeTable<T>() where T : class
    {
        string createTableSql = SqlGenerator.GenerateCreateTableSql<T>();
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            connection.Execute(createTableSql);
        }
    }
}

// 使用示例
// string connStr = "Server=127.0.0.1;Database=test_db;User Id=sa;Password=123456;";
// var initializer = new DbInitializer(connStr);
// initializer.InitializeTable<User>();

Dapper场景下的数据库迁移实现

数据库迁移指的是随着业务迭代,实体类结构发生变化时,同步更新数据库表结构,比如新增字段、修改字段类型、删除字段等。Dapper没有内置迁移工具,我们可以通过记录迁移版本、对比实体结构和现有表结构的方式实现迁移。

1. 迁移版本管理

首先需要在数据库中创建一个迁移记录表,用来存储已经执行过的迁移版本信息,避免重复执行迁移脚本。

// 迁移记录表实体
[Table("db_migration_history")]
public class MigrationHistory
{
    [Column("id", "int", true, false, true)]
    public int Id { get; set; }

    [Column("migration_version", "varchar(50)", false, false)]
    public string MigrationVersion { get; set; }

    [Column("apply_time", "datetime", false, false)]
    public DateTime ApplyTime { get; set; }

    [Column("description", "varchar(200)", false, true)]
    public string Description { get; set; }
}

2. 表结构差异对比

迁移的核心是对比当前实体类结构和数据库中已有的表结构,生成差异SQL。我们可以通过查询系统表获取现有表的字段信息,再和解析出的实体字段信息对比,生成新增、修改字段的SQL。

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Dapper;

public class MigrationGenerator
{
    // 获取数据库现有表的字段信息
    public static List<ColumnInfo> GetExistingColumns(string tableName, SqlConnection connection)
    {
        string sql = @"
            SELECT 
                c.name AS ColumnName,
                t.name AS DbType,
                c.is_nullable AS IsNullable,
                CASE WHEN pk.column_id IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey
            FROM sys.columns c
            JOIN sys.types t ON c.user_type_id = t.user_type_id
            JOIN sys.tables tb ON c.object_id = tb.object_id
            LEFT JOIN (
                SELECT i.object_id, ic.column_id
                FROM sys.indexes i
                JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
                WHERE i.is_primary_key = 1
            ) pk ON c.object_id = pk.object_id AND c.column_id = pk.column_id
            WHERE tb.name = @TableName";
        return connection.Query<ColumnInfo>(sql, new { TableName = tableName }).ToList();
    }

    // 生成迁移SQL(仅处理新增字段的场景,修改、删除可类似扩展)
    public static List<string> GenerateMigrationSql<T>(SqlConnection connection) where T : class
    {
        string tableName = EntityParser.GetTableName<T>();
        var existingColumns = GetExistingColumns(tableName, connection);
        var entityColumns = EntityParser.GetColumnInfos<T>();
        List<string> migrationSqls = new List<string>();

        // 找出实体中有但数据库中没有的字段,生成新增字段SQL
        foreach (var entityCol in entityColumns)
        {
            var existCol = existingColumns.FirstOrDefault(c => c.ColumnName == entityCol.ColumnName);
            if (existCol == null)
            {
                string nullableStr = entityCol.IsNullable ? "NULL" : "NOT NULL";
                string addColumnSql = $"ALTER TABLE {tableName} ADD {entityCol.ColumnName} {entityCol.DbType} {nullableStr}";
                migrationSqls.Add(addColumnSql);
            }
        }

        return migrationSqls;
    }
}

3. 执行迁移流程

完整的迁移执行流程包括:检查迁移记录、生成迁移SQL、执行SQL、记录迁移版本。我们可以把迁移逻辑封装成统一的方法,在应用启动时执行。

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Dapper;

public class MigrationRunner
{
    private readonly string _connectionString;

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

    // 执行迁移
    public void RunMigration<T>(string version, string description) where T : class
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            // 先检查迁移版本是否已执行
            string checkSql = "SELECT COUNT(1) FROM db_migration_history WHERE migration_version = @Version";
            int count = connection.ExecuteScalar<int>(checkSql, new { Version = version });
            if (count > 0)
            {
                return;
            }

            // 生成迁移SQL
            var migrationSqls = MigrationGenerator.GenerateMigrationSql<T>(connection);
            if (migrationSqls.Count == 0)
            {
                return;
            }

            // 执行迁移SQL
            foreach (var sql in migrationSqls)
            {
                connection.Execute(sql);
            }

            // 记录迁移版本
            string insertSql = "INSERT INTO db_migration_history (migration_version, apply_time, description) VALUES (@Version, @ApplyTime, @Description)";
            connection.Execute(insertSql, new { Version = version, ApplyTime = DateTime.Now, Description = description });
        }
    }
}

// 使用示例
// string connStr = "Server=127.0.0.1;Database=test_db;User Id=sa;Password=123456;";
// var runner = new MigrationRunner(connStr);
// runner.RunMigration<User>("v1.0.1", "新增用户年龄字段");

注意事项

  • 不同数据库的SQL语法存在差异,比如MySQL的自增关键字是AUTO_INCREMENT,PostgreSQL是SERIAL,生成SQL时需要根据使用的数据库做适配。
  • 生产环境执行迁移前,一定要先备份数据库,避免结构变更导致数据丢失。
  • 字段修改、删除等操作风险较高,建议先生成SQL人工审核后再执行,不要直接自动执行。
  • 如果实体类结构变化频繁,也可以考虑结合FluentMigrator等轻量迁移工具,减少自定义代码的工作量。

Dapper代码优先database_migrationORM修改时间:2026-07-05 09:48:52

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。