You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需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}");
}

为什么这个方案更好?

  1. 类型安全:编译期就能发现类型错误、属性名拼写错误,不用等到运行时才排查问题
  2. 集中管理:所有表结构元数据都绑定在Model属性上,不用维护分散的常量、字典
  3. 复用性高:工具类可以复用在所有Model上,新增表只需要写对应的Model类,不用重复写转换逻辑
  4. 可读性强:Model类直接对应数据库表,看代码就能清楚表结构,比一堆字典直观多了

内容的提问来源于stack exchange,提问作者Curtwagner1984

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:55:51