手撸ORM浅谈ORM框架之Add篇

时间:2024-09-02 17:06:14

快速传送

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

手撸ORM浅谈ORM框架之Add篇

手撸ORM浅谈ORM框架之Update篇

手撸ORM浅谈ORM框架之Delete篇

手撸ORM浅谈ORM框架之Query篇

手撸ORM浅谈ORM框架之Add篇后续待定。。。。。。

合抱之木,生于毫末

反射

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

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

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

泛型

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

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

九层之台,起于累土

主要通过反射获取实体信息,目前项目中的实体唯一主键统一使用的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数据库字符集的格式;

 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 }

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 }

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

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

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