【手撸一个ORM】第二步、封装实体描述和实体属性描述
一、实体属性描述 [MyProperty.cs]
- Name,属性名称
- PropertyInfo,反射获取的属性信息,后面很多地方需要通过该属性获取对应的实体类型,或调用SetValue进行赋值
- FieldName,对应的数据表列名
- IsKey,是否主键
- IsMap,查询时是否映射该属性,若属性非值类型或string,则默认为false,其他默认为true;若需手动设置为false,如计算属性,需要在MyColumnAttribute中配置 IsMap=false
- InsertIgnore,插入时忽略
- UpdateIgnore,更新时忽略
- JoinAble,是否可以通过Join进行查询,等同于导航属性
- ForignKey,如果可以通过Join查询,对应的外键名
- MasterKey,如果可以通过Join查询,对应的主表Id,默认为Id,若主表主键列名不是Id,需手动在MyForeignKey中配置 MasterKey="StudentId"
using System.Reflection; namespace MyOrm.Reflections { public class MyProperty { public string Name { get; set; } public PropertyInfo PropertyInfo { get; set; }public string FieldName { get; set; } public bool IsKey { get; set; } public bool IsMap { get; set; } = true; public bool InsertIgnore { get; set; } public bool UpdateIgnore { get; set; } public bool JoinAble { get; set; } public string ForeignKey { get; set; } public string MasterKey { get; set; } public MyProperty(PropertyInfo property) { Name = property.Name; TypeName = property.PropertyType.Name; PropertyInfo = property; if (property.IsMapAble()) { // 判断是否主键 var keyAttribute = property.GetKeyAttribute(); if (keyAttribute != null) { // 有 IsKey = true; FieldName = string.IsNullOrWhiteSpace(keyAttribute.FieldName) ? Name : keyAttribute.FieldName; if (keyAttribute.IsIncrement) { // 如果是自增列,不能插入和修改 InsertIgnore = true; UpdateIgnore = true; } else { // 如果不是自增列,可插入但不能修改 InsertIgnore = true; } } else if (Name == "Id") { FieldName = "Id"; IsKey = true; InsertIgnore = true; UpdateIgnore = true; } else { // 可映射的属性 var columnAttribute = property.GetMyColumnAttribute(); if (columnAttribute != null) { FieldName = string.IsNullOrWhiteSpace(columnAttribute.ColumnName) ? Name : columnAttribute.ColumnName; InsertIgnore = columnAttribute.Ignore || columnAttribute.InsertIgnore; UpdateIgnore = columnAttribute.Ignore || columnAttribute.UpdateIgnore; } else { FieldName = Name; } } } else if (property.IsJoinAble()) { // 可关联查询的属性 IsMap = false; JoinAble = true; UpdateIgnore = true; InsertIgnore = true; var foreignAttribute = property.GetForeignKeyAttribute(); if (foreignAttribute == null) { ForeignKey = Name + "Id"; MasterKey = "Id"; } else { ForeignKey = string.IsNullOrWhiteSpace(foreignAttribute.ForeignKey) ? Name + "Id" : foreignAttribute.ForeignKey; MasterKey = string.IsNullOrWhiteSpace(foreignAttribute.MasterKey) ? "Id" : foreignAttribute.MasterKey; } } else { // 其他属性 IsMap = false; UpdateIgnore = true; InsertIgnore = true; } } } }
二、数据实体描述 [MyEntity.cs]
- KeyColumn,数据表中主键列名称
- Name,实体名称
- TableName,实体对应的数据表名称
- IsSoftDelete,是否软删除(继承ISoftDelete),后面会有说明
- IsCreateAudit,是否创建审计(保存创建人、创建时间)
- IsUpdateAudit,是否更新审计(保存修改人、修改时间)
- Properties,封装过的属性信息列表-考虑过这里用字典保存,但是因为后面需要大量的遍历操作,个人感觉还是List用起来方便,所以最终选择了List类型
using MyOrm.Attributes; using MyOrm.Commons; using System; using System.Collections.Generic; namespace MyOrm.Reflections { public class MyEntity { public string KeyColumn { get; set; } public string Name { get; set; } public string TableName { get; set; } public bool IsSoftDelete { get; set; } public bool IsCreateAudit { get; set; } public bool IsUpdateAudit { get; set; } public List<MyProperty> Properties { get; set; } public MyEntity(Type type) { Name = type.Name; IsSoftDelete = type.IsInstanceOfType(typeof(ISoftDelete)); IsCreateAudit = type.IsInstanceOfType(typeof(ICreateAudit)); IsUpdateAudit = type.IsInstanceOfType(typeof(IUpdateAudit)); var tableAttr = type.GetCustomAttributes(typeof(MyTableAttribute), false); if (tableAttr.Length > 0) { var tableName = ((MyTableAttribute)tableAttr[0]).TableName; TableName = string.IsNullOrWhiteSpace(tableName) ? type.Name.Replace("Entity", "") : tableName; } else { TableName = Name; } Properties = new List<MyProperty>(); foreach (var propertyInfo in type.GetProperties()) { var property = new MyProperty(propertyInfo); if (property.IsKey) { KeyColumn = property.FieldName; } Properties.Add(property); } } } }
三、实体容器
上面对实体及其属性进行了封装,但是如果每次都需要反射获取,那性能损耗会非常厉害,因此将生成的内容缓存在起来便十分必要了。这里使用的是线程安全的静态字典作为缓存容器。
using System; using System.Collections.Concurrent; namespace MyOrm.Reflections { public class MyEntityContainer { private static readonly ConcurrentDictionary<string, MyEntity> Dict = new ConcurrentDictionary<string, MyEntity>(); public static MyEntity Get(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); if (Dict.TryGetValue(type.FullName ?? throw new InvalidOperationException(), out var result)) { return result; } else { var entity = new MyEntity(type); Dict.TryAdd(type.FullName, entity); return entity; } } } }