.NETORM框架设计(利用抽象、多态实现无反射的绿色环保ORM框架)

简介: 最近一直在忙新公司的基础库建设,对系统架构、开发框架及快速开发平台的设计实施都积累了一定的实践经验。 一般的中小型的软件开发公司,如果按照技术储备来衡量软件项目的技术含量的评定依据是可行的。但如果光是按照人头来衡量软件的技术含量是不可靠的。

最近一直在忙新公司的基础库建设,对系统架构、开发框架及快速开发平台的设计实施都积累了一定的实践经验。

一般的中小型的软件开发公司,如果按照技术储备来衡量软件项目的技术含量的评定依据是可行的。但如果光是按照人头来衡量软件的技术含量是不可靠的。所以我们在选择跳巢的时候是选择大公司还是选择有技术含量的公司要根据自己的职业规划来。(本人最近体会到的一点跳巢经验分享给大家)

由于我现有单位技术部门刚刚成立不久,需要一些基础的开发框架,ORM当然是跑不了的。在后面的文章中我将陆续写下我在建设基础框架中的一些实践检验,里面可能包括对UI层的封装、基础控件的封装等等。我就废话少扯了,进入主题。

这篇文章的重点是无反射的ORM框架,为什么会有这样的想法?其实前不久群里的朋友就问了一些问题,他们在构建自己的ORM框架的时候频繁的在使用反射来编写功能。从跟他们的交流上来看他们似乎很喜欢使用反射来写功能,但是没有仔细的研究过ORM框架的作用是在系统架构的哪个位置,在对性能要求十分严格的情况下反射会有点无能为力。

反射的好处当然是毋庸置疑的,一些技术稍微好点的或者大牛们通常会用动态编译的技术来平滑的过度这个系统最重要的性能瓶颈点。我总觉的可以用高层的抽象和约定来解决这个ORM使用反射的问题。下面我们来分析一下通常ORM框架为什么需要用反射,反射的目的是什么。[王清培版权所有,转载请给出署名]

ORM中反射的目的是什么?

当然我们这里讨论的是最普通的问题也是必须的问题。

ORM框架的种类形态各异,不同的公司不同的ORM实现。其实目的是为了能有一套属于自己公司的开发框架,这不是技术所定而是公司高层领导所要求的。(我们没有说话的权利,为了保住饭碗,我们只能听从指挥)

但是大部分的ORM框架的设计思想和实现思路都离不开那几点的“思维实现约束”。我为什么要说“思维实现约束”,这也是我们程序员的一些通病之一吧,都喜欢用复杂的技术。不管三七二十一用了心里舒服。这是好事,为了练习技术可以理解。没有这份好奇心这份激情我们也很难走到专家的位置。

目的之一:为了表达实体与表的对应关系

ORM是实体与表的一种映射关系,逐渐被发展为一种复杂的技术实现模型。

在传统的分层架构中,在实体的定义上都会使用一个特性来标记该实体所表示的表名称是什么。如:

[Serializable()]

[HZ.Table(TableName = "member")]

public class Member{}

特性HZ.Table中的属性TableName来保存静态的表名,在ORM中通过获取对象的类型然后反射出该类型的特性元数据。然后读取相关成员属性值,作为拼接SQL语句的必备条件。

目的之二:为了表达属性与字段的对应关系及一些主、外键

ORM中将实体的属性映射成数据库中表的字段,一般通过两种方式来表达这中关系。

第一种:通过属性特性来表示该属性代表的字段名称;

[HZ.Column(PrimaryKey = true,ColumnName=”MemberId”)]

public string MemberCode { get; set; }

第二种:直接通过属性名称来表示字段的名称;

public string MemberId { get; set; }

目的之三:获取实体属性中的值

在进行插入或更新的时候需要获取实体中的属性的值,这个时候只能使用反射的方式获取到属性的值,然后拼接插入或更新语句。

目的之四:设置实体属性的值

通过实例化泛型对象,然后反射对象的属性通过SetValue方法设置属性的值。

简结:这几点是最常用的,可能还包括其他复杂的功能,这里我就不涉及了。上面这几点都是通过反射获取实体的信息,不管是增、删、改、查都需要反射。尤其是对于查询数据来说,如果是大数据量的查询性能问题很吓人。

通过抽象、多态设计不需要特性的ORM实体

大部分ORM框架是需要代码生成器做支持的,不是所有的代码都是需要程序员手动去敲的,可以通过一些模板引擎类的代码生成器,编辑好自己的模板然后生成大部分的实体代码。包括.NET里面的EntityFramework、LinqToSql也是用IDE集成的代码生成器。

所以这里就会涉及到对企业代码生成器的考虑,这里就先不扯了,后续文章我们再来针对性讨论。

那么我们先来讨论如何设计实体结构,让它能包含我们ORM所需要的必备信息。其实我们的思路稍微转变一下利用抽象来解决问题。提高抽象层次,将实体视为两个层面。顶层抽象类被ORM使用,子类被调用者使用。

图:

我们的要求就是ORM中不能存在一个反射的代码。所以我们约定了BasicEntityObject抽象类,通过定义顶层抽象基类来包含子类所要用到的一些属性信息。

我们看一下抽象类中包含了哪些东西。

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Glory.Net.ORM
{
    public abstract class BaseEntityObject : DictionaryBase
    {
        /// <summary>
        /// 实体对象对应数据库中的表名
        /// </summary>
        private string _tablename = string.Empty;
        /// <summary>
        /// 受保护字典:实体类中对应数据库表中主键的属性和属性类型
        /// </summary>
        protected Dictionary<string, string> _primarydictionary = new Dictionary<string, string>();
        /// <summary>
        /// 用于实体子类设置当前子类对应数据库中的表名
        /// </summary>
        protected string TableName
        {
            get { return _tablename; }
            set { _tablename = value; }
        }
        /// <summary>
        /// 客户代码获取当前实例对应ORM中的表名
        /// </summary>
        public string GetTableName
        {
            get { return _tablename; }
        }
        /// <summary>
        /// 用于实体子类设置当前实例的属性值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected object this[string key]
        {
            get { return this.Dictionary[key]; }
            set { this.Dictionary[key] = value; }
        }
        /// <summary>
        /// 设置实例的属性值
        /// </summary>
        /// <param name="key">属性名称</param>
        /// <param name="value">属性值</param>
        public void SetEntityVlues(string key, object value)
        {
            if (this.Dictionary.Contains(key))
            {
                this.Dictionary[key] = value;
            }
        }
        /// <summary>
        /// 获取实例的属性键值队
        /// </summary>
        /// <returns></returns>
        public IDictionary GetEntityValue
        {
            get{return (IDictionary)this.Dictionary;}
        }
        /// <summary>
        /// 获取实例的主键信息
        /// </summary>
        public IDictionary GetEntityPrimary
        {
            get { return (IDictionary)_primarydictionary; }
        }
        protected abstract void AddPrimaryToDictionary();
    }
}

其实代码很简单,就是为了将子类的属性值保存到基类中来,让子类只是一个空壳子。

public class Tb_Car : VluesInitTb_Car
    {
        /// <summary>
        /// 唯一主键
        /// </summary>
        public string CID
        {
            get { return this["CID"] as string; }
            set { this["CID"] = value; }
        }
        /// <summary>
        /// 车牌号
        /// </summary>
        public string CarBanrdCode
        {
            get { return this["CarBanrdCode"] as string; }
            set { this["CarBanrdCode"] = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string CarCode
        {
            get { return this["CarCode"] as string; }
            set { this["CarCode"] = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string DriverName
        {
            get { return this["DriverName"] as string; }
            set { this["DriverName"] = value; }
        }
        /// <summary>
        /// 联系电话
        /// </summary>
        public string Mobile
        {
            get { return this["Mobile"] as string; }
            set { this["Mobile"] = value; }
        }
        /// <summary>
        /// 车型
        /// </summary>
        public string CarType
        {
            get { return this["CarType"] as string; }
            set { this["CarType"] = value; }
        }
        /// <summary>
        /// 购车日期
        /// </summary>
        public DateTime? BuyDateTime
        {
            get { return this["BuyDateTime"] as DateTime?; }
            set { this["BuyDateTime"] = value; }
        }
        /// <summary>
        /// 所属中心编号。外键
        /// </summary>
        public string AttachCenter
        {
            get { return this["AttachCenter"] as string; }
            set { this["AttachCenter"] = value; }
        }
        /// <summary>
        /// 所属区部编号。外键
        /// </summary>
        public string AttachSection
        {
            get { return this["AttachSection"] as string; }
            set { this["AttachSection"] = value; }
        }
        /// <summary>
        /// 所属站点编号。外键
        /// </summary>
        public string AttachStop
        {
            get { return this["AttachStop"] as string; }
            set { this["AttachStop"] = value; }
        }
    }

那么中间的类是干嘛用的呢,是为了将初始化隔离在基类中;

[Serializable()]
    public class VluesInitTb_Car : BaseEntityObject
    {
        public VluesInitTb_Car()
        {
            this.TableName = "Tb_Car";
            /// <summary>
            /// 唯一主键
            /// </summary>
            this.Dictionary.Add("CID", null);
            /// <summary>
            /// 车牌号
            /// </summary>
            this.Dictionary.Add("CarBanrdCode", null);
            /// <summary>
            /// 
            /// </summary>
            this.Dictionary.Add("CarCode", null);
            /// <summary>
            /// 
            /// </summary>
            this.Dictionary.Add("DriverName", null);
            /// <summary>
            /// 联系电话
            /// </summary>
            this.Dictionary.Add("Mobile", null);
            /// <summary>
            /// 车型
            /// </summary>
            this.Dictionary.Add("CarType", null);
            /// <summary>
            /// 购车日期
            /// </summary>
            this.Dictionary.Add("BuyDateTime", null);
            /// <summary>
            /// 所属中心编号。外键
            /// </summary>
            this.Dictionary.Add("AttachCenter", null);
            /// <summary>
            /// 所属区部编号。外键
            /// </summary>
            this.Dictionary.Add("AttachSection", null);
            /// <summary>
            /// 所属站点编号。外键
            /// </summary>
            this.Dictionary.Add("AttachStop", null);
            AddPrimaryToDictionary();
        }

        /// <summary>
        /// 实体类 重写实体基类添加主键信息方法,主键数据类型首字母要大写
        /// </summary>
        protected override void AddPrimaryToDictionary()
        {
            _primarydictionary.Add("CID", "string");
        }

通过这种层次的抽象可以很好的规避特性带来的性能问题。在ORM中我们的泛型方法都是约束实体为BaseEntityObject类型,然后所有的信息包括主键、字段、数据类型都能够通过多态的方式获取到。

ORM通过实例化一个对象的实例然后将其缓存起来,作为后续使用。而不需要频繁的实例化中间对象带来的性能问题。

其实大部分的代码都是可以通过代码生成器生成的,我们也正在为公司开发符合自己公司产品的代码生成器,包括对业务代码的高度抽象、业务建模后的代码生成。

当然该篇文章只是简单的讲解了一下核心的内容,也算是抛砖引玉吧。希望对大家来说有点启发作用。[王清培版权所有,转载请给出署名]

目录
相关文章
|
10天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
2月前
|
开发框架 JavaScript 前端开发
5个.NET开源且强大的快速开发框架(帮助你提高生产效率)
5个.NET开源且强大的快速开发框架(帮助你提高生产效率)
|
4月前
|
开发框架 缓存 Cloud Native
微软发布 .NET 云原生开发框架—— .NET Aspire
微软于 2023-11-14日 发布了 .NET 8 的正式版。伴随着这个重要 .NET 版本的发布,微软也发布了一个全新的 .NET云原生开发框架 —— .NET Aspire,它提供了如下 3 个方面的能力,来帮助我们使用 .NET 开发分层、云就绪的可观测、本地与生产环境一致的分布式云原生应用程序。
194 0
|
28天前
|
开发框架 网络协议 .NET
深入.net框架
深入.net框架
11 0
|
2月前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报
|
3月前
|
PHP Windows
php扩展com_dndnet(PHP与.NET框架进行交互)
php扩展com_dndnet(PHP与.NET框架进行交互)
php扩展com_dndnet(PHP与.NET框架进行交互)
|
3月前
|
开发框架 前端开发 JavaScript
一款基于.NET Core的快速开发框架、支持多种前端UI、内置代码生成器
一款基于.NET Core的快速开发框架、支持多种前端UI、内置代码生成器
|
3月前
|
SQL JavaScript NoSQL
3个.NET开源简单易用的任务调度框架
3个.NET开源简单易用的任务调度框架
103 0
|
3月前
|
C#
.NET Core中灵活使用反射
.NET Core中灵活使用反射
|
3月前
|
数据采集 开发框架 JavaScript
C#/.NET/.NET Core优秀项目和框架2023年12月简报
C#/.NET/.NET Core优秀项目和框架2023年12月简报