学习如何使用 ORMX 的表管理功能,包括创建、更新、删除表,以及索引管理。 关键字:表管理,索引管理,复合索引,表结构更新,索引命名规则,自动架构更新
表管理器(ITableManager)
表管理器负责管理数据库表的创建、删除和更新。使用表管理器可以手动创建、删除和更新表。
ITableManager 接口
public interface ITableManager
{
// 获取表名
string GetName(Type entityType);
// 检查表是否存在
bool Exists(Type entityType);
bool Exists(string tableName);
// 创建表
void Create(Type entityType);
void Create(Type entityType, string tableName);
// 更新表结构
void Update(Type entityType);
// 删除表
void Drop(Type entityType);
void Drop(string tableName);
// 获取表对象
ITable<T> Table<T>() where T : class, new();
}
获取表名
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
var tableName = tableManager.GetName(typeof(User));
Console.WriteLine(tableName); // 输出:User
检查表是否存在
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
// 检查实体类型对应的表是否存在
var exists = tableManager.Exists(typeof(User));
Console.WriteLine(<div class="latex">$"User 表存在:{exists}");
// 检查表名是否存在
var exists = tableManager.Exists("Users");
Console.WriteLine($</div>"Users 表存在:{exists}");
创建表
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
// 创建表(使用实体类型的默认表名)
tableManager.Create(typeof(User));
// 创建表(指定表名)
tableManager.Create(typeof(User), "AppUsers");
Console.WriteLine("表创建成功");
创建表并自动更新架构
ORMX 支持在创建表时自动检测并更新表结构。当模型结构发生变化时(如添加新字段、修改字段类型等),可以通过传递 autoUpdate 参数来自动同步数据库结构:
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
// 第一次调用:创建表
tableManager.Create(typeof(User));
// 模型结构发生变化后,再次调用并传入 true:自动更新表结构
tableManager.Create(typeof(User), true);
Console.WriteLine("表创建或更新成功");
自动更新功能说明:
工作原理:
- 系统会将表的元数据(字段信息、索引配置等)保存在
__ormx_schema_metadata表中 - 当调用
Create(type, true)时,会比较当前模型结构与存储的元数据 - 如果检测到差异,会自动执行数据库结构变更(添加列、修改列类型、更新索引等)
- 更新完成后,会自动更新元数据表
- 系统会将表的元数据(字段信息、索引配置等)保存在
支持的变更类型:
- ✅ 添加新列
- ✅ 删除列(部分数据库支持)
- ✅ 修改列类型
- ✅ 添加/删除索引
- ✅ 修改索引配置
数据库兼容性:
- ✅ SQLite:完全支持(通过表重建机制)
- ✅ MySQL:完全支持
- ✅ PostgreSQL:完全支持
- ✅ SQL Server:完全支持
注意事项:
- 自动更新功能会导致元数据表
__ormx_schema_metadata的创建 - 对于 SQLite,修改列类型需要重建表,系统会自动处理数据迁移
- 建议在生产环境中谨慎使用自动更新,最好在开发阶段使用
- 自动更新功能会导致元数据表
更新表
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
// 更新表结构(添加新列、修改列类型等)
tableManager.Update(typeof(User));
Console.WriteLine("表更新成功");
删除表
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
// 删除实体类型对应的表
tableManager.Drop(typeof(User));
// 删除指定表名的表
tableManager.Drop("User");
Console.WriteLine("表删除成功");
表对象(ITable)
表对象是进行数据库操作的主要接口,通过表管理器获取。
获取表对象
var userTable = tableManager.Table<User>();
ITable 接口
public interface ITable<T> : IBaseBuilder<T>, IInsertClause<T>, IDebugClause<T> where T : class, new()
{
bool Drop();
bool Truncate();
}
表对象的常用操作
var userTable = tableManager.Table<User>();
// 插入数据
var user = userTable.Insert(new User { Name = "张三", Email = "zhangsan@example.com" });
// 查询数据
var users = userTable.Where(u => u.Age > 25).GetList();
// 更新数据
user.Age = 26;
userTable.Update(user);
// 删除数据
userTable.Delete(user.Id);
// 清空表
userTable.Truncate();
// 删除表
userTable.Drop();
关于 CRUD 操作的详细用法,请查阅 CRUD 操作。
手动创建表
如果需要完全控制表结构,可以使用 SQL 命令手动创建:
using JCode.ORMX.DbProvider.SQLite;
using JCode.ORMX.Core;
using Microsoft.Data.Sqlite;
// 创建数据库连接
using var connection = new SqliteConnection("Data Source=test.db");
connection.Open();
var dbProvider = new SQLiteProvider(connection);
var sqlExecutor = dbProvider.SqlExecutor;
// 创建表
sqlExecutor.ExecuteNonQuery(@"
CREATE TABLE Users (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Username TEXT NOT NULL,
Email TEXT NOT NULL UNIQUE,
Age INTEGER DEFAULT 0,
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
);
");
// 创建索引
sqlExecutor.ExecuteNonQuery("CREATE INDEX idx_users_email ON Users(Email);");
// 创建表管理器
using var tableManager = dbProvider.GetTableManager();
// 获取表对象
var userTable = tableManager.Table<User>();
// 执行操作
userTable.Insert(new User { Username = "张三", Email = "zhangsan@example.com" });
查看表结构
using JCode.ORMX.DbProvider.SQLite;
using JCode.ORMX.Core;
using Microsoft.Data.Sqlite;
// 创建数据库连接
using var connection = new SqliteConnection("Data Source=test.db");
connection.Open();
var dbProvider = new SQLiteProvider(connection);
var sqlExecutor = dbProvider.SqlExecutor;
// SQLite 查看表结构
var tableInfo = sqlExecutor.ExecuteReader<TableInfo>("PRAGMA table_info(Users);");
foreach (var column in tableInfo)
{
Console.WriteLine(<div class="latex">$"列名:{column.Name}, 类型:{column.Type}");
}
// 辅助类
private class TableInfo
{
public string Name { get; set; }
public string Type { get; set; }
}
表结构更新
当实体类添加新属性时,可以更新表结构:
// 原始实体类
public class User
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
[Column(Name = "Username")]
public string? Username { get; set; }
[Column(Name = "Email")]
public string? Email { get; set; }
}
// 添加新属性
public class User
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
[Column(Name = "Username")]
public string? Username { get; set; }
[Column(Name = "Email")]
public string? Email { get; set; }
// 新增属性
[Column(Name = "Phone")]
public string? Phone { get; set; }
[Column(Name = "LastLoginAt")]
public DateTime? LastLoginAt { get; set; }
}
// 更新表结构
using var connection = new SqliteConnection("Data Source=test.db");
connection.Open();
var dbProvider = new SQLiteProvider(connection);
using var tableManager = dbProvider.GetTableManager();
// 添加新列
tableManager.Update(typeof(User));
索引管理
ORMX 支持通过 ColumnAttribute 属性自动创建和管理索引。
索引属性说明
在 ColumnAttribute 中,以下属性用于配置索引:
| 属性 | 类型 | 说明 |
|---|---|---|
Indexed |
bool | 是否创建普通索引 |
IndexName |
string | 自定义索引名(可选) |
IndexDescending |
bool | 是否降序索引(默认 false) |
IsUnique |
bool | 是否创建唯一索引 |
UniqueIndexName |
string | 自定义唯一索引名(可选) |
自动创建索引
在创建表时,ORMX 会根据 ColumnAttribute 的配置自动创建索引:
public class User
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
// 普通索引 - 默认索引名:idx_User_Name
[Column(Indexed = true)]
public string? Name { get; set; }
// 普通索引 - 自定义索引名
[Column(Indexed = true, IndexName = "idx_custom_code")]
public string? Code { get; set; }
// 降序索引
[Column(Indexed = true, IndexDescending = true)]
public int SortOrder { get; set; }
// 唯一索引 - 默认索引名:uk_User_Email
[Column(IsUnique = true)]
public string? Email { get; set; }
// 唯一索引 - 自定义索引名
[Column(IsUnique = true, UniqueIndexName = "uk_custom_phone")]
public string? Phone { get; set; }
// 同时创建普通索引和唯一索引
[Column(Indexed = true, IsUnique = true)]
public string? Identifier { get; set; }
}
// 创建表时自动创建所有索引
tableManager.Create(typeof(User));
索引更新
当实体类的索引配置发生变化时,可以通过 Update 方法更新索引:
// 修改索引配置
public class User
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
// 移除索引
public string? Name { get; set; }
// 修改为唯一索引
[Column(IsUnique = true, UniqueIndexName = "uk_updated_code")]
public string? Code { get; set; }
// 新增索引
[Column(Indexed = true)]
public DateTime CreatedAt { get; set; }
}
// 更新表结构和索引
tableManager.Update(typeof(User));
手动管理索引(SQLite)
对于 SQLite 数据库,SQLiteTableManager 提供了以下手动索引管理方法:
var sqliteTableManager = (SQLiteTableManager)tableManager;
// 创建普通索引
sqliteTableManager.CreateIndex("User", "Name", "idx_custom_name");
// 创建唯一索引
sqliteTableManager.CreateIndex("User", "Email", "uk_custom_email", isUnique: true);
// 创建降序索引
sqliteTableManager.CreateIndex("User", "SortOrder", isDescending: true);
// 检查索引是否存在
var exists = sqliteTableManager.IndexExists("idx_custom_name");
// 获取表的所有索引
var indexes = sqliteTableManager.GetIndexes("User");
// 删除索引
sqliteTableManager.DropIndex("idx_custom_name");
// 重建表的所有索引
sqliteTableManager.RebuildIndexes("User");
索引命名规则
当未指定自定义索引名时,ORMX 会使用以下命名规则:
- 普通索引:
idx_<表名>_<列名> - 唯一索引:
uk_<表名>_<列名>
例如:
idx_User_Name(User 表 Name 列的普通索引)uk_User_Email(User 表 Email 列的唯一索引)
复合索引
ORMX 支持通过 IndexAttribute 在类级别定义复合索引(多列索引)。
IndexAttribute 属性说明
| 属性 | 类型 | 说明 |
|---|---|---|
Name |
string | 索引名(必填) |
Columns |
string | 索引列(逗号分隔的属性名,必填) |
IsUnique |
bool | 是否唯一索引(可选,默认 false) |
SortOrders |
string | 排序方向(逗号分隔,与 Columns 对应,可选) |
定义复合索引
ORMX 支持两种方式定义复合索引:
方式一:自动解析(推荐)
只需指定索引名,ORMX 会自动解析列名和是否唯一:
// 自动解析:Status, CreatedAt
[Index("idx_user_status_created")]
// 自动解析:Name, Email,且 IsUnique = true
[Index("uk_user_name_email")]
// 自动解析:Age
[Index("idx_user_age")]
public class User
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public int Age { get; set; }
public int Status { get; set; }
public DateTime CreatedAt { get; set; }
}
自动解析规则:
| 索引名格式 | 前缀 | 列名解析 | IsUnique |
|---|---|---|---|
idx_[表名]_[列 1] |
idx_ |
列 1 |
false |
idx_[表名]_[列 1]_[列 2] |
idx_ |
列 1, 列 2 |
false |
uk_[表名]_[列 1] |
uk_ |
列 1 |
true |
uk_[表名]_[列 1]_[列 2] |
uk_ |
列 1, 列 2 |
true |
示例:
idx_user_status_created→ 列:Status, CreatedAt,普通索引uk_user_name_email→ 列:Name, Email,唯一索引idx_order_customer_product_date→ 列:Customer, Product, Date,普通索引
方式二:显式指定
// 显式指定列名
[Index("idx_user_status_created", "Status, CreatedAt")]
[Index("idx_user_name_email", "Name, Email", IsUnique = true)]
[Index("idx_user_age_desc", "Age", SortOrders = "DESC")]
public class User
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public int Age { get; set; }
public int Status { get; set; }
public DateTime CreatedAt { get; set; }
}
// 创建表时自动创建所有复合索引
tableManager.Create(typeof(User));
复合索引排序方向
可以为复合索引的每一列指定不同的排序方向:
// CustomerId 升序,OrderDate 降序
[Index("idx_order_customer_date", "CustomerId, OrderDate", SortOrders = "ASC, DESC")]
[Index("idx_order_unique", "CustomerId, ProductId", IsUnique = true)]
public class Order
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
public int CustomerId { get; set; }
public int ProductId { get; set; }
public decimal Amount { get; set; }
public DateTime OrderDate { get; set; }
}
混合索引
可以同时使用单列索引和复合索引:
[Index("idx_mixed_type_status", "Type, Status")]
public class MixedIndexEntity
{
[Column(IsPrimaryKey = true, IsAutoIncrement = true)]
public int Id { get; set; }
// 单列索引
[Column(Indexed = true)]
public string? Name { get; set; }
// 唯一索引
[Column(IsUnique = true)]
public string? Code { get; set; }
// 复合索引的一部分
public int Type { get; set; }
public int Status { get; set; }
}
手动创建复合索引(SQLite)
对于 SQLite 数据库,SQLiteTableManager 提供了手动创建复合索引的方法:
var sqliteTableManager = (SQLiteTableManager)tableManager;
// 创建复合索引
sqliteTableManager.CreateCompositeIndex("User", "idx_manual_composite", new[] { "Name", "Age" });
// 创建唯一复合索引
sqliteTableManager.CreateCompositeIndex("User", "idx_manual_unique", new[] { "Name", "Email" }, isUnique: true);
// 创建带排序方向的复合索引(CustomerId 升序,Amount 降序)
sqliteTableManager.CreateCompositeIndex(
"Order",
"idx_manual_sort",
new[] { "CustomerId", "Amount" },
sortOrders: new[] { false, true }); // false=ASC, true=DESC
错误处理
如果在 IndexAttribute 中指定了不存在的属性名,ORMX 会在运行时抛出详细的错误提示:
// 错误的索引定义
[Index("idx_invalid", "NonExistentColumn")]
public class User { ... }
// 创建表时会抛出:
// InvalidOperationException: 在实体类型 'User' 中定义的索引 'idx_invalid' 包含不存在的属性:NonExistentColumn
// 请检查 IndexAttribute 的 Columns 参数,确保所有列名都是有效的属性名。
实践示例
数据库连接和表管理
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序(推荐方式:传入连接字符串)
using var provider = new SQLiteProvider("Data Source=test.db");
// 获取表管理器(无需额外参数)
using var tableManager = provider.GetTableManager();
// 获取表对象
var userTable = tableManager.Table<User>();
var orderTable = tableManager.Table<Order>();
Console.WriteLine($</div>"用户表名:{userTable.TableName}");
Console.WriteLine(<div class="latex">$"订单表名:{orderTable.TableName}");
资源管理
// tableManager 实现了 IDisposable,需要使用 using 语句
using var tableManager = provider.GetTableManager();
// 获取表对象(Table<T> 不需要使用 using,因为它没有实现 IDisposable)
var userTable = tableManager.Table<User>();
var orderTable = tableManager.Table<Order>();
// 执行操作...
// tableManager 释放时会自动释放内部的 SqlExecutor 资源
// provider 释放时也会释放内部管理的 SqlExecutor 资源
说明:
tableManager和provider都实现了IDisposable接口,建议使用using语句tableManager.Dispose()会释放其内部创建的SqlExecutorBase实例provider.Dispose()会释放其内部管理的SqlExecutor实例Table<T>对象不需要手动释放
初始化表创建的最佳实践:
在创建初始化表(如系统表、配置表等)时,如果需要批量创建多个表,建议使用同一个 tableManager 实例,而不是为每个表创建新的 tableManager。这样可以避免创建大量的数据库连接,提高性能并减少资源消耗。
// 推荐:使用同一个 tableManager 创建多个表
using var tableManager = provider.GetTableManager();
foreach (var tableType in _tableTypes)
{
try
{
tableManager.Create(tableType, true);
Logger?.Debug($</div>"数据表创建成功 {tableType.Name}");
}
catch (Exception ex)
{
Logger?.Error($"创建数据表失败 {tableType.Name}: {ex.Message}", ex);
}
}
// 不推荐:为每个表创建新的 tableManager(会创建大量数据库连接)
foreach (var tableType in _tableTypes)
{
using (var tm = provider.GetTableManager())
{
tm.Create(tableType, true);
}
}
性能对比:
- 推荐方式:只创建 1 个数据库连接和 1 个
SqlExecutorBase实例 - 不推荐方式:如果有 10 个表,会创建 10 个数据库连接和 10 个
SqlExecutorBase实例
虽然数据库连接池可以复用连接,但频繁创建和释放连接仍然会带来性能开销。在初始化场景下,使用同一个 tableManager 是更高效的选择。
自动架构更新示例
using JCode.ORMX.DbProvider.SQLite;
// 创建数据库提供程序
using var provider = new SQLiteProvider("Data Source=test.db");
using var tableManager = provider.GetTableManager();
// 第一次运行:创建表
tableManager.Create(typeof(User));
// 开发过程中,User 类添加了新属性 Phone
// 再次运行:自动检测并添加 Phone 字段
tableManager.Create(typeof(User), true);
// 或者使用 Update 方法手动更新
tableManager.Update(typeof(User));
总结
本章介绍了 ORMX 框架的表管理功能,包括表的创建、更新、删除和索引管理。通过 ITableManager 接口,开发者可以方便地管理数据库表的生命周期。ORMX 提供了自动索引管理功能,支持单列索引、复合索引、唯一索引等多种索引类型,并提供了灵活的索引命名规则和排序方向配置。
自动架构更新功能是 ORMX 的核心特性之一,它允许开发者在模型结构发生变化时自动同步数据库结构,无需手动编写迁移脚本。系统会将表结构元数据保存在 __ormx_schema_metadata 表中,并在每次调用 Create(type, true) 时自动检测和更新差异。
对于 SQLite 数据库,还提供了手动索引管理的扩展方法。合理使用索引可以显著提升查询性能,但需要注意索引的数量和维护成本。
扩展思考
在大数据量场景下,如何设计最优的索引策略?是否需要引入数据库分区、分表等高级技术来应对数据增长?在分布式系统中,如何处理跨节点的索引同步和一致性问题?对于 MongoDB 这样的文档数据库,索引策略与传统关系型数据库有何不同?这些问题值得在深入使用 ORMX 后进一步思考。