首页 程序笔记 数据库ORM框架原理和实现

数据库ORM框架原理和实现

数据查询

传统的ado.net查询表

public List<User> FindAllUsers() {
            using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString))
            {
                string sql = "SELECT * FROM USERS";
                SqlCommand cmd = new SqlCommand(sql,conn);
                conn.Open();
                var reader = cmd.ExecuteReader();
                List<User> list = new List<User>();
                while (reader.Read()) {
                    User item = new User()
                    {
                        Id = (int)reader["Id"],
                        NickName = reader["NickName"].ToString()
                    };
                    list.Add(item);
                }
                return list;
            }
        }

这种写法,数据库有多少表就得写多少个这样的方法,接下来我们使用泛型+反射就可以一个方法支持所有表的查询。

泛型+反射动态生成Sql语句

public List<T> FindAll<T>() {
            var type = typeof(T);
            using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString))
            {
                string columnStrings = string.Join(",", string.Join(",", type.GetProperties().Select(x => $"[{x.Name}]")));
                string sql = $"SELECT {columnStrings} FROM [{type.Name}]";
                SqlCommand cmd = new SqlCommand(sql, conn);
                conn.Open();
                var reader = cmd.ExecuteReader();
                List<T> list = new List<T>();
                while (reader.Read())
                {
                    T item = (T)Activator.CreateInstance(type);
                    foreach (var prop in type.GetProperties()) {
                        prop.SetValue(item, reader[prop.Name] is DBNull ? null : reader[prop.Name]);
                    }
                    list.Add(item);
                }
                return list;
            }
        }

实体名称与数据库表名称不对应,实体属性名称与数据库表字段名称不对应的问题

通常情况下,数据库表名一般用复数,而程序种实体名称一般用单数,那么直接用type.Name生成的SQL语句执行就会报错。目前主流的解决方法是使用特性Attribute标注。

//表名特性
public class DBTableAttribute : Attribute
{
    public string Name { get; set; }
    public DBTableAttribute(string name) {
        this.Name = name;
    }
}
//字段特性
public class DBColumnAttribute:Attribute
{
    public string Name { get; set; }
  public DBColumnAttribute(string name)
  {
    this.Name = name;
  }
    public class BaseModel
    {
        public int? Id { get; set; }
    }
[DBTable("Users")]
    public class User:BaseModel
    {
        public string NickName { get; set; }
        [DBColumn("Lv")]
        public int? Level { get; set; }
    }
var type = typeof(T);
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString))
{
     //GetColumnName方法,先检查是否有标记特性,如果有就返回特性名称,没有就返回属性名称
     string columnStrings = string.Join(",", string.Join(",", type.GetProperties().Select(x => $"[{x.GetColumnName()}]")));
     //GetTableName方法,先检查是否有标记特性,如果有就返回特性名称,没有就返回属性名称
     string tableName = type.GetTableName();
     string sql = $"SELECT {columnStrings} FROM [{tableName}]";
     SqlCommand cmd = new SqlCommand(sql, conn);
     conn.Open();
     var reader = cmd.ExecuteReader();
     List<T> list = new List<T>();
     while (reader.Read())
     {
           T item = (T)Activator.CreateInstance(type);
           foreach (var prop in type.GetProperties()) {
                 prop.SetValue(item, reader[prop.GetColumnName()] is DBNull ? null : reader[prop.GetColumnName()]);
           }
           list.Add(item);
      }
      return list;
}
//扩展方法 先检查是否有标记特性,如果有就返回特性名称,没有就返回属性名称
public static string GetColumnName(this PropertyInfo prop) {
if (prop.IsDefined(typeof(DBColumnAttribute), true))
 {
   DBColumnAttributeattribute = prop.GetCustomAttribute<DBColumnAttribute>();
     return attribute.Name;
 }
 else {
     return prop.Name;
 }
}
public static string GetTableName(this Type type) {
    if (type.IsDefined(typeof(DBTableAttribute), true))
    {
        DBTableAttribute attribute = type.GetCustomAttribute<DBTableAttribute>();
        return attribute.Name;
    }
    else {
        return type.Name;
    }
}

这样最后生成的SQL语句就是SELECT [Id],[NickName],[Lv] FROM [Users],完全和数据库匹配了。

这些基本功能就实现了,我们可以优化一下代码,抽象特性类,扩展方法也可以合并一下,代码如下:

public abstract class AbstracDBAttribute:Attribute
    {
        public string Name { get; set; }
        public AbstracDBAttribute(string name)
        {
            this.Name = name;
        }
    }
    public class DBTableAttribute : AbstracDBAttribute {
        public DBTableAttribute(string name) : base(name){
        }
    }
    public class DBColumnAttribute : AbstracDBAttribute {
        public DBColumnAttribute(string name) : base(name) { 
        }
    }
//GetTableName和GetColunmName可以合并成一个方法了
public static string GetAttributeName(this MemberInfo member) {
            if (member.IsDefined(typeof(AbstracDBAttribute), true))
            {
                AbstracDBAttribute attribute = member.GetCustomAttribute<AbstracDBAttribute>();
                return attribute.Name;
            }
            else
            {
                return member.Name;
            }
        }

以上是最简单的数据库ORM原理,后面还可以增加查询条件,支持分页,支持Lambda拉姆达表达式查询等,接着往下看。

插入数据

还是利用泛型完成通用,反射动态生成SQL语句。遍历对象的属性,拼接SQL语句时,要注意主键自增列不要出现再SQL语句中,可以增加特性标记主键和自增列,遍历属性的时候进行过滤。这里如果属性值为Null,我们就不生成到Sql语句中。代码如下:

    public class BaseModel
    {
        [PrimaryKey]
        [Identity]
        public int? Id { get; set; }
    }
/// <summary>
    /// 主键
    /// </summary>
    public class PrimaryKeyAttribute : Attribute {
        
    }
    /// <summary>
    /// 标识列
    /// </summary>
    public class IdentityAttribute : Attribute {
        
    }
public int Insert<T>(T t)
{
    Type type = typeof(T);
    var props = type.GetProperties().FilterKey();
    List<string> cloumnList = new List<string>();
    List<string> valueList = new List<string>();
    List<SqlParameter> parameters = new List<SqlParameter>();
    foreach (var prop in props) {
        var val = prop.GetValue(t);
        if (val != null) {
             cloumnList.Add($"[{prop.GetAttributeName()}]");
             valueList.Add($"@{prop.GetAttributeName()}");
             parameters.Add(new SqlParameter($"@{prop.GetAttributeName()}", val));
        }
    }
    string sql = $"INSERT INTO [{type.GetAttributeName()}] ({string.Join(",", cloumnList.ToArray())}) VALUES ({string.Join(",", valueList.ToArray())})";
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString))
    {
        SqlCommand cmd = new SqlCommand(sql, conn);
        cmd.Parameters.AddRange(parameters.ToArray());
        conn.Open();
        return cmd.ExecuteNonQuery();
    }
}
/// <summary>
/// 筛选非自增列
/// </summary>
/// <param name="props"></param>
/// <returns></returns>
public static IEnumerable<PropertyInfo> FilterKey(this IEnumerable<PropertyInfo> props) {
    return props.Where(x => !x.IsDefined(typeof(IdentityAttribute), true));
}

注意,这里VLUES的内容不能直接拼接字符串,会有SQL注入问题,这里使用参数化的方法。PS:添加参数的时候要注意,SqlParameter的值不能是Null,如果一定要传入一个Null参数,可以转化为DBNull.Value。

站星网

数据查询传统的ado.net查询表public List<User> FindAllUsers() { using (SqlConnection conn = new SqlCon..

为您推荐

数据库表设计

字节首先需要达成共识的是:1个Byte字节等于8个bit位。bit是最小一级的信息单位,可以表示一个0或1(即二进制);字符字符其实是一个统称,字母、数字、运算符号、标点符号和其他符号,以及一些功能性符号都属于字符..

让 AI 真正好用:一个框架提升你的办公效率

很多人觉得 AI 帮不上忙,但问题往往不在 AI 本身。在日常工作中,人们经常以一种“看似明确、实则含糊”的方式与 AI 沟通:就像用咖啡泡面去煎牛排,无论如何都不会得到理想结果。即便已经频繁使用 AI:方案依然像..

.NET中全新的MongoDb ORM框架 - SqlSugar

.NET中好用的MongoDb ORM很少,选择也很少,所以我打造了一款适合SQL习惯的MongoDb ORM,让用户多一个选择。1、 MongoDB ORM教程1.1 NUGET 安装SqlSugar.MongoDbCoreSqlSugarCore1.2 已支持功能单表CRUD+分页+排序+..

openHalo:国产数据库Halo正式开源,迈向高性能兼容新时代

近日,国产数据库Halo迎来重要里程碑,正式宣布开源,项目命名为 openHalo,并已在GitHub上线(openHaloHitHub地址:https://github.com/HaloTech-Co-Ltd/openHalo)。openHalo旨在提供一个兼容MySQL协议、基于Postg..

PGlite:轻量级嵌入式PostgreSQL数据库使用方法

PGlite是一款基于PostgreSQL的轻量级嵌入式数据库,专为前端应用、无服务器环境和本地开发优化。与传统的PostgreSQL服务器相比,PGlite无需单独安装数据库服务,而是可以直接在应用程序内部运行,提供了一种更加灵活..

服务器安装数据库MySQL8.0版本,打包导入到MySQL5.6失败的结局方式

最近数据库升级为mysql8.0,在使用过程中发现一些问题,首先mysql8.0有很多新特性,对服务器配置要求较高,所有就考虑把数据库版本切换到MySQL5.6,经过多出测试处理发现在8.0数据库打包的数据导入到5.6总是报错,或..

在数据库中cms_content表content字段用SQL过滤替换掉包含photo.abc.tw所有图片img标签

SQL 语句:UPDATEcms_contentSETcontent=REGEXP_REPLACE(content,'<img[^>]*src="photo\\.abc\\.tw[^"]*"[^>]*>','')WHEREcontentREGEXP'<img[^>]*src="//photo\\.abc\\.tw';解释:REGEXP_R..

值得探索的 8 个机器学习 JavaScript 框架

JavaScript开发人员倾向于寻找可用于机器学习模型训练的JavaScript框架。下面是一些机器学习算法,基于这些算法可以使用本文中列出的不同JavaScript框架来模型训练:简单的线性回归多变量线性回归逻辑回归朴素贝叶斯..

数据库SQL优化大总结之 百万级数据库优化方案

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from..

2025年常见SQLServer数据库面试题

分享一些 2025年常见的 SQL Server 数据库面试题,涵盖基础知识、性能优化、高级查询、管理与运维等多个方面,适用于开发、DBA 及数据分析相关岗位的面试。1. SQL Server 的基本架构是什么?答案:SQL Server 的架构..

使用 html2canvas 实现截图功能

html2canvas 是一个开源的 JavaScript 库,用于将网页上的 HTML 元素渲染成图像。它通过遍历页面的 DOM 树和计算样式,然后将其绘制到 <canvas> 元素上,最终生成图片。该库不依赖服务器端,而是通过浏览器端的 Java..

网站统计中的访问信息收集的前端实现

网站数据统计分析工具是网站站长和运营人员经常使用的一种工具,比较常用的有谷歌分析、百度统计和腾讯分析等等。所有这些统计分析工具的第一步都是网站访问数据的收集。目前主流的数据收集方式基本都是基于javascri..

使用SuperWebSocket实现Web消息推送

在大部分Web系统中,我们可能遇到需要向客户端推送消息的需求。SuperWebSocket第三方库能让我们轻松的完成任务。SuperWebSocket第三方库可以从网上下载,不过通过Visual Studio Nuget安装更快。引用SuperWebSocket相..

.NET C# 使用Hook钩子实现全局监听键盘和鼠标

C# 是一种面向对象的编程语言,具有丰富的类库和工具支持,适用于各种类型的应用程序开发。Windows 提供了一种称为"钩子"(Hook)的机制,允许拦截并处理系统级别的事件,如键盘按键和鼠标移动。通过结合 C# 和 Hook..

BotSharp 基于 .NET 平台的开源 AI 聊天机器人框架

BotSharp 是一个开源的、基于 .NET 平台的 AI 聊天机器人框架,旨在简化构建智能对话系统的过程。它主要通过自然语言处理(NLP)技术,帮助开发者构建具备语言理解和对话能力的应用。BotSharp 提供了丰富的功能和扩..

.NET C#连接FTP实现文件上传下载

在 .NET 中可以使用 System.Net.FtpWebRequest 类来连接 FTP 服务器,实现文件上传和下载。以下是实现文件上传和下载的完整代码示例。1. 上传文件到 FTP 服务器using System;using System.IO;using System.Net;class..

C#使用 Attribute 实现 AOP 功能

在 C# 中,通过自定义 Attribute 并结合一些技术(如动态代理、反射等)可以实现 AOP(面向切面编程)。AOP 通常用于日志记录、性能监控、权限验证等横切关注点。以下是一个使用 C# Attribute 实现 AOP 功能的示例。..

使用CSS columns-visibility实现砌体布局

CSS的 columns 属性(如 columns、column-count 和 column-width)通常用于多列文本布局,而不是直接用于砌体布局。然而,结合 columns 和 visibility 属性,可以在某些情况下实现类似砌体布局的效果,虽然它并不完..

ASP.NET 使用Entity Framework (EF) 创建迁移修改SQLite数据库表结构

在 ASP.NET 中,使用 Entity Framework (EF) 创建并连接 SQLite 数据库是一种轻量级、高效的数据库管理方式。以下是详细步骤:安装必要的 NuGet 包安装EntityFrameworkCore.Sqlite包:Install-Package Microsoft.Ent..

.NET 游戏开发框架有哪些?

在游戏开发领域,.NET 框架因其强大的功能和灵活性而广受欢迎。对于希望使用 .NET 进行游戏开发的开发者来说,了解可用的框架是至关重要的。以下是一些流行的 .NET 游戏开发框架:1. Unity: 尽管 Unity 主要使用 C# ..

发表回复

返回顶部

微信分享

微信分享二维码

扫描二维码分享到微信或朋友圈

链接已复制
塔尊佛教网|借视频之舟,渡烦恼之海 Tazun.Cn佛教音乐网 - 海量佛乐、梵呗、禅音在线试听与下载经书网 - 以音声作佛事,聆听与观想的修学园地 jingshu.net佛教导航 - 开启智慧之旅,连接十方法缘 | fjdh.org.cn智慧莲华 - 赋能寺院数字化升级,打造智慧弘道平台趣知道 - 提问与分享,人人都是知识分享家 | Quzhidao.Com地藏孝亲网--南无大愿地藏王菩萨给农网地藏经顺运堂 - 专业家居风水布局,八字命理分析,助您家宅兴旺,运势亨通弘善佛教网-传播正信正知佛法的佛教网站国学在线 - 国学网,国学学校,国学经典,国学地图品读名篇佳句,涵养诗意人生 - 古诗词网哦嘿养殖网 - 热门乡村养殖发展项目_养殖技术知识分享生死书 - 佛教文化传承与生命智慧探索平台地藏论坛-佛教网络净土_佛法综合社区生死书生死书