zoukankan      html  css  js  c++  java
  • 手撸ORM浅谈ORM框架之Add篇

    快速传送

    手撸ORM浅谈ORM框架之基础篇

    手撸ORM浅谈ORM框架之Add篇

    手撸ORM浅谈ORM框架之Update篇

    手撸ORM浅谈ORM框架之Delete篇

    手撸ORM浅谈ORM框架之Query篇

    后续待定。。。。。。

    合抱之木,生于毫末

    反射

    在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

    手撸ORM浅谈ORM框架系列使用反射机制来实现的动态获取所需要的信息,反射机制有优点也有缺点,使用了反射我们可以不用每次更新实体模型数据访问层转换Sql等很多需要修改的地方,享受快捷便利的同时肯定会带来相应的缺点,反射的性能在执行同样的操作时会略低于直接使用强类型语言的原生特性,毕竟它至少多了动态获取信息步骤;我们不能因此否认反射,反射确实在很多场景可以为我们做很多不必要的重复性工作,可以节约出时间做更棘手的问题。

    对于我们需要使用什么决定因素需要看使用前后的变化,利大于弊且符合业务需要就放心使用,完美不存在,更美从未止步。。。

    泛型

    泛型(摘录百度百科:泛型)

    由于.NET Framework 泛型的类型参数之实际类型在运行时均不会被消除,运行速度会因为类型转换的次数减少而加快。Java 泛型的参数只可以代表类,不能代表个别对象。由于 Java 泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。Java 编译器在编译泛型时会自动加入类型转换的编码,故运行速度不会因为使用泛型而加快。

    透过现象看本质

    (2020-10-30新增)
    为什么通过反射就可以实现把任意实体写入数据库?其实,根据当前请求传入的实体通过反射拿到当前实体的public属性字段以及每个public字段的值,当我们拿到了当前实体的public属性字段及字段的值,我们就可以根据当前实体的主键是否写入数据库来动态的生成Insert into插入语句(自动递增、指定主键、复合主键等),ORM框架本身并不是万能的,只是根据当前实体主键定义规则动态的帮助我们生成Sql语句,ORM框架也可以在生成Sql语句后把Sql语句打印出来或提供写到日志文件等,获得程序的执行Sql语句可以更好地帮助我们优化或者更好地使用ORM框架。当然,打印执行的Sql语句应该有一个一键开关的功能想想就是美好的~

    九层之台,起于累土

    主要通过反射获取实体信息,目前项目中的实体唯一主键统一使用的bigint自动递增。

    BaseRepository-》GetCurrentTableName获取表名称;

     1 /// <summary>
     2 /// get current-table-name
     3 /// </summary>
     4 /// <returns>return table-name</returns>
     5 private string GetCurrentTableName()
     6 {
     7     string currentTableName = string.Empty;
     8     var t = typeof(T);
     9     if (t != null && !t.Name.IsNullOrEmpty())
    10     {
    11         currentTableName = t.Name;
    12     }
    13     if (currentTableName.IsNullOrEmpty())
    14     {
    15         throw new ArgumentNullException("get table-name is null");
    16     }
    17     return currentTableName;
    18 }

    BaseRepository-》GetExcludeKeyAllFields获取主键以外的public字段;

     1 /// <summary>
     2 /// get exclude key all fields
     3 /// </summary>
     4 /// <returns>return exclude key all fields</returns>
     5 private List<PropertyInfo> GetExcludeKeyAllFields()
     6 {
     7     PropertyInfo[] properties = typeof(T).GetProperties();
     8     // filter abstract virtual property
     9     properties = properties.Where(p => p.PropertyType.IsAbstract == false && !p.GetMethod.IsVirtual == true).ToArray();
    10     if (properties == null || properties.Length <= 0)
    11     {
    12         throw new ArgumentNullException("public value fields is null");
    13     }
    14     List<PropertyInfo> list = new List<PropertyInfo>();
    15     foreach (var item in properties)
    16     {
    17         if (item.CustomAttributes.Any(c => c.AttributeType.Name == nameof(KeyAttribute)))
    18         {
    19             continue;
    20         }
    21         list.Add(item);
    22     }
    23     if (list == null || list.Count <= 0)
    24     {
    25         throw new ArgumentNullException("public value fields is null");
    26     }
    27     return list;
    28 }

    BaseRepository-》GetValue获取字段对应的值,String和Char前面分别加了N前缀(Sql Server反射弧),MySql中文字符可加或不加中文字段不会出现乱码,若出现乱码可以根据实际情况设置MySql数据库字符集的格式;

    2020-10-21初版-》Sql Server中文字符有可能会出现写入数据库产生乱码,中文值部分可以参照使用: N'赵钱孙李';

     1 /// <summary>
     2 /// get value
     3 /// </summary>
     4 /// <param name="property"></param>
     5 /// <param name="entity"></param>
     6 /// <returns></returns>
     7 private string GetValue(PropertyInfo property , T entity)
     8 {
     9     var val = property.GetValue(entity);
    10     if (val == null)
    11     {
    12       return "NULL";
    13     }
    14     else
    15     {
    16         string prefixN = string.Empty;
    17         if (property.PropertyType.Name == nameof(String) || property.PropertyType.Name == nameof(Char))
    18         {
    19             prefixN = "N";
    20         }
    21         if (property.PropertyType.Name == nameof(Boolean))
    22         {
    23             return string.Format("{0}", GetBoolValue(val));
    24         }
    25         return string.Format("{0}'{1}'", prefixN, val);
    26     }
    27 }

    2020-10-28(增加)MySql推荐使用下面的方法;

     1 /// <summary>
     2 /// get value
     3 /// </summary>
     4 /// <param name="property"></param>
     5 /// <param name="entity"></param>
     6 /// <returns></returns>
     7 private string GetValue(PropertyInfo property , T entity)
     8 {
     9     var val = property.GetValue(entity);
    10     if (val == null)
    11     {
    12       return "NULL";
    13     }
    14     else
    15     {
    16         if (property.PropertyType.Name == nameof(Boolean))
    17         {
    18             return string.Format("{0}", GetBoolValue(val));
    19         }
    20         return string.Format("'{0}'", val);
    21     }
    22 }

    BaseRepository-》GetBoolValue值属性转换,Sql Server中数据类型bit:'true' or 'false' 等同于'1' or '0';MySql 8.x数据类型bit: 需要把bool 'true' or 'false' 转换成'1' or '0' 。目前项目中使用到的数据类型bigint、varchar、int、char、datetime、bit,bit->需要转换;

     1 /// <summary>
     2 /// get bool value
     3 /// mysql true or false convert 1 or 0
     4 /// </summary>
     5 /// <param name="obj"></param>
     6 /// <returns></returns>
     7 private int GetBoolValue(object obj)
     8 {
     9     //return obj.ToString().ToLower() == "false" ? 0 : 1;
    10     if (obj.ToString().ToLower() == "false")
    11     {
    12         return 0;
    13     }
    14     return 1;
    15 }

    (2020-10-30新增start)

    我们先来看看Insert Sql语句:

    1. INSERT INTO TABLE_NAME VALUES (值1, 值2,....)  --表的每一列都需要给值;

    2. INSERT INTO TABLE_NAME (列1, 列2,...) VALUES (值1, 值2,....)  --指定列插入指定值。

    前面叙述了那么多我们的目的还是单纯的,我们只是想在当前的请求传入的实体做文章 。如何获取实体的表名称、实体public非主键字段以及它们的值;主键需要单独处理一下,实体的主键是自动递增、指定主键还是复合主键,本篇是最简单的自动递增,所以,Insert语句没有主键列,其他复杂的情况我们可以定义xxxKeyAttribute来区分当前键是自动递增、指定主键、复合主键等。

    (2020-10-30新增end)

    BaseRepository-》GetInsertSql一路走来Sql出现了(提高性能可以优化,缓存当前项目所有表的增删查改Sql语句)

     1 /// <summary>
     2 /// get insert sql
     3 /// </summary>
     4 /// <param name="entity">entity</param>
     5 /// <returns>return insert sql</returns>
     6 private string GetInsertSql(T entity)
     7 {
     8     string tableName = GetCurrentTableName();
     9     StringBuilder sbField = new StringBuilder();
    10     StringBuilder sbValue = new StringBuilder();
    11     PropertyInfo[] properties = GetAllFields(true);
    12     foreach (var item in properties)
    13     {
    14         sbField.AppendFormat("{0},", item.Name);
    15         sbValue.AppendFormat("{0},", GetValue(item, entity));
    16     }
    17     sbField.Remove(sbField.Length - 1, 1);
    18     sbValue.Remove(sbValue.Length - 1, 1);
    19     //todo ;SELECT @@identity return identity
    20     return string.Format("INSERT INTO {0} ({1}) VALUES ({2})", tableName, sbField, sbValue);
    21 }

    BaseRepository-》Add千呼万唤始出来,终于到写入数据库了(Add成功后没有返回实体因为NET Core的DbContext取消了SqlQuery,基类BaseRepository实现Add后返回当前的实体主键,基本原生 SQL 查询 可使用 FromSqlRaw 扩展方法基于原始 SQL 查询开始 LINQ 查询。 FromSqlRaw 只能在直接位于 DbSet<> 上的查询根上使用;NET Framework DBContext中有此方法SqlQuery,可以使用Query在Insert语句后面;SELECT @@identity返回int、bigint类型自动递增主键并赋值给当前实体,如果是指定主键直接返回当前实体,也期待园友提出更好的解决方案)

     1 /// <summary>
     2 /// add entity
     3 /// </summary>
     4 /// <param name="entity">entity</param>
     5 /// <returns>return true or false</returns>
     6 public bool Add(T entity)
     7 {
     8     string sql = GetInsertSql(entity);
     9     context.Database.ExecuteSqlRaw(sql);
    10     return true;
    11 }

    实操Repository方法泛型约束

     1 /// <summary>
     2 /// LearnStudentRepository
     3 /// </summary>
     4 public partial class LearnStudentRepository: ILearnStudentRepository
     5 {
     6     public bool Add(Learn_Student learnStudent)
     7     {
     8         using (MySqlDbContext mySqlDbContext=new MySqlDbContext())
     9         {
    10             BaseRepository<Learn_Student> baseRepository = new BaseRepository<Learn_Student>(mySqlDbContext);
    11             return baseRepository.Add(learnStudent);
    12         }
    13     }
    14 }

    登高望远,更上层楼

    learn-orm-net缺少以下情况的处理:

    • 1. Sql语句没有缓存,缓存提高一些性能 (使用缓存提高一些性能2020-10-26);
    • 2.不支持指定主键的实体,既是已经提前把主键生成好了;
    • 3.项目使用的实体是自动递增主键并且没有返回主键的值,不能满足主子表有外键关系业务场景,既是子表存主表的主键(自动递增类型主键);
    • 4.复合主键(主键都是指定主键,提前按照一定规则生成的键值);
    • 5.复合主键里面包含自增主键,既是有指定类型主键也有自动递增类型主键;
    • 6.实体包含导航属性级联写入数据库(类似于3)
    • (如果业务需要特殊的方式,ORM框架没有我们可以写基类方法或者扩展方法来适应项目需要,待补充...)
    • 注:learn-orm-net目前只是作为学习ORM框架原理的Demo,项目会做出一定的优化处理,但不能直接拿来在项目中使用,毕竟现在NET Framework、NET Core已经有很多优秀的ORM框架,NET下一次发布就是只有一个版本了,我们没有必要重复造轮子,造轮子是因为没有现成的优秀的轮子可用。

    如果只是停留在会使用当前项目所使用的ORM框架基本增删查改,对于根据业务更好的使用ORM框架是有点困难的;所以,深入理解ORM原理,为未来的某个时刻我们遇到了问题,更好的根据ORM框架有的功能做出比较符合业务需要的程序;或者,扩展当前ORM框架没有的功能来适应我们项目的业务需求。

    代码下载地址: SourceCode  作者水平有限欢迎园友纠正错误及不恰当之处,予以及时修正以免误导他人!

     
  • 相关阅读:
    201571030334/201571030323《小学四则运算练习软件软件需求说明》结对项目报告
    201571030334/201571030323实验三 软件工程结对项目
    201571030334 小学四则运算练习软件项目报告
    201571030334 快速通读教材《构建之法》后的五个疑惑
    个人学期总结
    201571030333/201571030132《小学四则运算练习软件需求说明》结对项目报告
    201571030333/201571030132《小学四则运算》结对项目报告
    201571030333 小学生四则运算软件的开发
    关于阅读《构建之法》的疑惑
    四则运算的调查问卷
  • 原文地址:https://www.cnblogs.com/dingshuanglei/p/13823685.html
Copyright © 2011-2022 走看看