百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

C#.Net如何手写ORM

bigegpt 2024-09-16 12:28 8 浏览

快速认识ORM

对象-关系映射,即Object/Relation Mapping,主要实现程序对象到关系数据库的映射。现在.Net比较流行的ORM框架有:EF、SqlSugar、Dapper、FreeSql、Nhibernate、IBatis.Net等。

O/RM只是一层代码的封装,底层还是基于ADO.NET完成对数据库的访问。

一般写法

如果我们要写一个查询,用ADO.NET就会如下这样写。

private static string ConnectionStringCustomers = "Data Source=.;Database=Customers;" +    "User ID=sa;Password=123456;MultipleActiveResultSets=True"; public Company FindCompany(int id){    string sql = $@"        SELECT [Id],[Name],[CreateTime],[CreatorId],               [LastModifierId],[LastModifyTime]        FROM [dbo].[Company]         WHERE ID = {id}";    using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))    {        SqlCommand command = new SqlCommand(sql, conn);        conn.Open();        var reader = command.ExecuteReader();        if (reader.Read())        {            Company company = new Company()            {                Id = (int)reader["Id"],                Name = reader["Name"].ToString()            };            return company;        }        else        {            return null;        }    }}
public abstract class BaseModel{    public int Id { set; get; }}   public class Company : BaseModel{    public string Name { get; set; }     public DateTime CreateTime { get; set; }     public int CreatorId { get; set; }     public int? LastModifierId { get; set; }     public DateTime? LastModifyTime { get; set; }}

但这样的写法是写死了的,我们能不能写一个通用查询,不管他是哪张表。

通用查询

既然要通用,那就不能写死类型,我们想到了使用泛型。泛型是任何类型都可以用,为了保证类型正确,我们再加泛型约束。
为了得到属性,我们要使用反射获取。

public T Find<T>(int id) where T : BaseModel // 泛型约束,必须继承自BaseModel{    string colums = string.Join(",", typeof(T).GetProperties().Select(p => #34;[{p.Name}]").ToArray());    string sql = #34;SELECT {colums} FROM [{typeof(T).Name}] WHERE Id={id}";     using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))    {        SqlCommand command = new SqlCommand(sql, conn);        conn.Open();        var reader = command.ExecuteReader();        if (reader.Read())        {            // 反射实例化            T t = Activator.CreateInstance<T>();            foreach (var property in typeof(T).GetProperties())            {                property.SetValue(t, reader[property.Name] is DBNull ? null : reader[property.Name]);            }            return t;        }        else        {            return null;        }    }    return default(T);}

上述的方法,使用泛型和反射使我们的查询可以通用。
然后使用
Company Company = sqlHelper.Find<Company>(1);来调用得到实体。

但是,我们还有一个问题,如果我们的表名和实体类名称不一样,或字段名不一样,比如:表名为Sys_Company而实体名为Company,那我们该如何映射?
这里我们打算用C#的特性来解决这一问题。

首先,创建用来映射的特性类。

public class AbstractMappingAttribute : Attribute{    public string MappingName = null;    public AbstractMappingAttribute(string mappingName)    {        this.MappingName = mappingName;    }}

映射表名。

[AttributeUsage(AttributeTargets.Class)]public class DBProxyTableAttribute: AbstractMappingAttribute{    public DBProxyTableAttribute(string tableName) : base(tableName){}}

映射列名。

    [AttributeUsage(AttributeTargets.Property)]    public class DBProxyColumnAttribute : AbstractMappingAttribute    {        public DBProxyColumnAttribute(string columnName):base(columnName) {}    }

在类名上添加特性。

[DBProxyTable("Sys_Company")]public class Company : BaseModel{    [DBProxyColumn("Company_Name")]    public string Name { get; set; }    ......}

获取实体类名或属性上的特性值来映射数据库的方法。

public static class DBProxyMappingExtension{    public static string GetMappingName(this MemberInfo member)    {        string name = null;        if (member.IsDefined(typeof(AbstractMappingAttribute), true))        {            var attribute = member.GetCustomAttribute<AbstractMappingAttribute>();            name = attribute.MappingName;        }        else        {            name = member.Name;        }        return name;    }}

最后,重新修改通用方法。

public T Find<T>(int id) where T : BaseModel // 泛型约束,必须继承自BaseModel{    //string colums = string.Join(",", typeof(T).GetProperties().Select(p => #34;[{p.Name}]").ToArray());    string colums = string.Join(",", typeof(T).GetProperties().Select(p => #34;[{p.GetMappingName()}]").ToArray());     //string sql = #34;SELECT {colums} FROM [{typeof(T).Name}] WHERE Id={id}";    string sql = #34;SELECT {colums} FROM [{typeof(T).GetMappingName()}] WHERE Id={id}";     using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))    {        SqlCommand command = new SqlCommand(sql, conn);        conn.Open();        var reader = command.ExecuteReader();        if (reader.Read())        {            // 反射实例化            T t = Activator.CreateInstance<T>();            foreach (var property in typeof(T).GetProperties())            {                //property.SetValue(t, reader[property.Name] is DBNull ? null : reader[property.Name]);                property.SetValue(t, reader[property.GetMappingName()] is DBNull ? null : reader[property.GetMappingName()]);            }            return t;        }        else        {            return null;        }    }    return default(T);}

通用插入

我们先写一个泛型缓存类:

public class SQLCacheBuilder<T> where T : BaseModel{    private static string InsertSQL = "";    /// <summary>    /// 静态构造函数,由CLR保障,在第一次使用该类之前,完成调用且只调用一次    /// </summary>    static SQLCacheBuilder()    {        Console.WriteLine("SQLCacheBuilder 静态ctor。。。。。。。");        string columns = string.Join(",", typeof(T).GetPropertiesNoKey()            .Select(p => #34;[{p.GetMappingName()}]"));        string values = string.Join(",", typeof(T).GetPropertiesNoKey()            .Select(p => #34;@{p.GetMappingName()}"));            InsertSQL = #34;INSERT INTO [{typeof(T).GetMappingName()}] " +                #34;({columns}) VALUES ({values})";    }     public static string GetInsertSQL()    {        return InsertSQL;    }}

当第一次调用SQLCacheBuilder方法对InsertSQL赋值,那么再次调用就会直接取缓存中的InsertSQL。但如果调用SQLCacheBuilder<T>类,传来的泛型T不同,则缓存的InsertSQL是不同的。InsertSQL就是我们要执行的sql语句。

我们数据库表设置的id是自增的,为了在插入的SQL中过滤掉Id字段,我们打算用特性过滤。

[AttributeUsage(AttributeTargets.Property)]public class DBProxyKeyAttribute: Attribute{} public static class DBProxyFilterAttributeExtension{    public static IEnumerable<PropertyInfo> GetPropertiesNoKey(this Type type)    {        return type.GetProperties()            .Where(p => !p.IsDefined(typeof(DBProxyKeyAttribute), true));    }}

然后在实体属性id上加上此特性:

     [DBProxyKey]     public int Id { set; get; }

这样只要调用了GetPropertiesNoKey方法,遇见属性上加有DBProxyKey特性则会过滤掉。

最后,就是我们的通用的插入数据的方法:

// 写一个通用插入方法public bool Insert<T>(T t) where T : BaseModel{    // 调用SQLCacheBuilder方法获得拼接的sql    string sql = SQLCacheBuilder<T>.GetInsertSQL();    // 为了防止拼接的有sql注入,使用参数parameters    var parameters = typeof(T).GetPropertiesNoKey()        .Select(p => new SqlParameter(#34;@{p.GetMappingName()}",         p.GetValue(t) is null ? DBNull.Value : p.GetValue(t)));           return this.ExecuteSQL<bool>(sql, parameters.ToArray(), command =>    {        int iResult = command.ExecuteNonQuery();        return iResult == 1;    });} private T ExecuteSQL<T>(string sql, SqlParameter[] parameters, Func<SqlCommand, T> func){    using (SqlConnection conn = new SqlConnection(ConnectionStringCustomers))    {        SqlCommand command = new SqlCommand(sql, conn);        command.Parameters.AddRange(parameters);        conn.Open();        return func.Invoke(command);    }}

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...