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

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