无需Linq To DB,如何在C#中合理定义数据库表模型?
我完全懂你现在的糟心处境——手动维护一堆常量、字典来对应数据库表结构,不仅写起来繁琐到爆炸,还特别容易踩坑:比如字典查错键名、类型转换出错,后来尝试枚举和Model类又把流程搞复杂了。不用Linq To DB的话,咱们可以用强类型模型+自定义特性+辅助工具类的方案,既保持类型安全,又能摆脱那些冗余的字典。
核心思路
把数据库表的每一行映射成一个强类型的Model类,用自定义特性标记每个属性对应的数据库列信息(列名、数据类型、约束、默认值等),再写一个通用工具类来处理表结构生成、数据映射这些重复工作,彻底告别手动维护字典的麻烦。
第一步:定义自定义特性存储列元数据
先写一个特性类,用来把数据库列的所有信息绑定到Model的属性上:
[AttributeUsage(AttributeTargets.Property)] public class DbColumnAttribute : Attribute { // 数据库列名 public string ColumnName { get; } // 数据库数据类型 public DbDataType DataType { get; } // 是否为主键 public bool IsPrimaryKey { get; set; } // 是否非空 public bool IsNotNull { get; set; } // 是否唯一 public bool IsUnique { get; set; } // 默认值 public string DefaultValue { get; set; } public DbColumnAttribute(string columnName, DbDataType dataType) { ColumnName = columnName; DataType = dataType; } } // 枚举数据库支持的基础数据类型 public enum DbDataType { Integer, Text, Boolean, DateTime }
第二步:编写强类型的Image Model类
把数据库表的每一列对应成Model的属性,用上面的特性标记所有元数据:
public class ImageModel { [DbColumn("image_id", DbDataType.Integer, IsPrimaryKey = true)] public int Id { get; set; } [DbColumn("image_name", DbDataType.Text)] public string Name { get; set; } [DbColumn("image_path", DbDataType.Text, IsNotNull = true, IsUnique = true)] public string Path { get; set; } [DbColumn("image_description", DbDataType.Text)] public string Description { get; set; } [DbColumn("image_thumbnail", DbDataType.Text)] public string Thumbnail { get; set; } [DbColumn("image_rating", DbDataType.Integer, DefaultValue = "0")] public int Rating { get; set; } [DbColumn("image_view_count", DbDataType.Integer, DefaultValue = "0")] public int ViewCount { get; set; } [DbColumn("image_watch_time_seconds", DbDataType.Integer, DefaultValue = "0")] public int WatchTimeSeconds { get; set; } [DbColumn("image_size_bytes", DbDataType.Integer, DefaultValue = "-1")] public long SizeBytes { get; set; } [DbColumn("image_width", DbDataType.Integer, DefaultValue = "-1")] public int Width { get; set; } [DbColumn("image_height", DbDataType.Integer, DefaultValue = "-1")] public int Height { get; set; } [DbColumn("image_megapixel", DbDataType.Integer, DefaultValue = "-1")] public int Megapixel { get; set; } [DbColumn("image_ffprobe_score", DbDataType.Integer, DefaultValue = "-1")] public int FfprobeScore { get; set; } [DbColumn("image_is_fav", DbDataType.Boolean, DefaultValue = "0")] public bool IsFav { get; set; } [DbColumn("image_date_fav", DbDataType.DateTime)] public DateTime? DateFav { get; set; } [DbColumn("image_date_created_at", DbDataType.DateTime)] public DateTime? DateCreatedAt { get; set; } [DbColumn("image_date_added_to_db_at", DbDataType.DateTime, IsNotNull = true, DefaultValue = "(DATETIME('now'))")] public DateTime DateAddedToDb { get; set; } [DbColumn("image_date_updated_at", DbDataType.DateTime, IsNotNull = true, DefaultValue = "CURRENT_TIMESTAMP")] public DateTime DateUpdatedAt { get; set; } [DbColumn("image_date_last_lookup_at", DbDataType.DateTime)] public DateTime? DateLastLookupAt { get; set; } }
第三步:编写通用工具类处理重复工作
写一个工具类,用来自动生成建表SQL、把DbDataReader映射成Model对象,不用手动写字典和转换逻辑:
public static class DbModelHelper { // 根据Model生成建表SQL public static string GenerateCreateTableSql<T>(string tableName) { var properties = typeof(T).GetProperties(); var columnDefinitions = new List<string>(); foreach (var prop in properties) { var dbColumnAttr = prop.GetCustomAttribute<DbColumnAttribute>(); if (dbColumnAttr == null) continue; var columnParts = new List<string>(); // 列名+数据类型 columnParts.Add($"`{dbColumnAttr.ColumnName}` {GetSqlDataType(dbColumnAttr.DataType)}"); // 主键约束 if (dbColumnAttr.IsPrimaryKey) columnParts.Add("PRIMARY KEY"); // 非空约束 if (dbColumnAttr.IsNotNull) columnParts.Add("NOT NULL"); // 唯一约束 if (dbColumnAttr.IsUnique) columnParts.Add("UNIQUE"); // 默认值 if (!string.IsNullOrEmpty(dbColumnAttr.DefaultValue)) columnParts.Add($"DEFAULT {dbColumnAttr.DefaultValue}"); columnDefinitions.Add(string.Join(" ", columnParts)); } return $"CREATE TABLE IF NOT EXISTS `{tableName}` ({string.Join(", ", columnDefinitions)});"; } // 把DbDataReader映射成Model对象 public static T MapToModel<T>(DbDataReader reader) where T : new() { var model = new T(); var properties = typeof(T).GetProperties(); foreach (var prop in properties) { var dbColumnAttr = prop.GetCustomAttribute<DbColumnAttribute>(); if (dbColumnAttr == null) continue; var columnIndex = reader.GetOrdinal(dbColumnAttr.ColumnName); if (reader.IsDBNull(columnIndex)) { // 处理可空类型 if (prop.PropertyType.IsNullable()) prop.SetValue(model, null); continue; } var value = reader.GetValue(columnIndex); // 自动转换类型(支持可空类型) var targetType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; prop.SetValue(model, Convert.ChangeType(value, targetType)); } return model; } // 辅助方法:获取SQL对应的类型字符串(这里以SQLite为例,可扩展支持其他数据库) private static string GetSqlDataType(DbDataType dataType) { return dataType switch { DbDataType.Integer => "INTEGER", DbDataType.Text => "TEXT", DbDataType.Boolean => "INTEGER", // SQLite用INTEGER存布尔值 DbDataType.DateTime => "DATETIME", _ => throw new ArgumentOutOfRangeException(nameof(dataType)) }; } // 辅助方法:判断是否为可空类型 private static bool IsNullable(this Type type) { return Nullable.GetUnderlyingType(type) != null; } }
第四步:实际使用示例
现在你可以用简洁的方式处理表创建和数据查询,全程强类型,再也不用担心字典的问题:
// 1. 生成Image表的建表SQL var createImageTableSql = DbModelHelper.GenerateCreateTableSql<ImageModel>("images"); // 2. 查询数据并映射成ImageModel对象 using var connection = new SqliteConnection("Data Source=your_db.db"); connection.Open(); using var command = new SqliteCommand("SELECT * FROM images WHERE image_id = @id", connection); command.Parameters.AddWithValue("@id", 1); using var reader = command.ExecuteReader(); if (reader.Read()) { var image = DbModelHelper.MapToModel<ImageModel>(reader); // 直接使用强类型的image对象,编译期就能发现错误,不用再查字典键名 Console.WriteLine($"图片名称:{image.Name},浏览次数:{image.ViewCount}"); }
为什么这个方案更好?
- 类型安全:编译期就能发现类型错误、属性名拼写错误,不用等到运行时才排查问题
- 集中管理:所有表结构元数据都绑定在Model属性上,不用维护分散的常量、字典
- 复用性高:工具类可以复用在所有Model上,新增表只需要写对应的Model类,不用重复写转换逻辑
- 可读性强:Model类直接对应数据库表,看代码就能清楚表结构,比一堆字典直观多了
内容的提问来源于stack exchange,提问作者Curtwagner1984
相关产品推荐
相关产品推荐

