首页 程序笔记 数据库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..

为您推荐

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# ..

PluginCore 基于 ASP.NET Core 的轻量级插件框架

项目概述PluginCore 是一个基于 ASP.NET Core 的轻量级插件框架,旨在简化插件的集成与管理。通过最少的配置,开发者可以快速集成并专注于业务逻辑的开发。它支持动态 WebAPI、插件隔离与共享、前后端分离、热插拔等..

SQLite性能支持多少数据量?

SQLite是一种轻量级的关系型数据库管理系统,广泛应用于移动应用、嵌入式系统和小型桌面应用程序中。由于其零配置、自给自足的特性,SQLite在很多场景下非常受欢迎。然而,对于许多开发者来说,一个常见的问题是:SQ..

.NET框架和CLR的工作原理?

.NET 框架和 CLR(公共语言运行时,Common Language Runtime)共同构成了一个应用程序运行和开发的环境,为多种编程语言提供跨平台支持、内存管理、异常处理、安全性、以及其他服务。它们各自的工作原理如下:.NET F..

使用ADO.NET连接到南大通用GBase 8s数据库

南大通用GBase 8s数据库广泛应用于各种企业级应用中,对于开发者而言,掌握如何使用ADO.NET连接到GBase 8s数据库非常重要。本文将详细阐述如何通过ADO.NET方式连接到南大通用GBase 8s数据库,并进行基本的数据库操作..

发表回复

返回顶部