Asp.Net Core Identity 骚断腿的究极魔改实体类
coredx 人气:0前言
默认的 Identity 实体类型在大多数时候已经基本够用,很多时候也只是稍微在 IdentityUser 类中增加一些自定义数据字段,比如头像。这次,我要向园友隆重介绍我魔改之后的 Identity 实体类,能支持一些特别风骚的操作。当然也完全兼容内置的 UserManager、RoleManager 和 SignInManager,毕竟也是从内置类型继承扩展出来的。
正文
魔改的实体类基于一组我自定义实体接口,这组接口我也实现了一组打包好的基础类型。因为 Identity 系列实体类型已经存在,而 C# 不支持多重继承,所以只能把这些代码在魔改的 Identity 实体类中粘贴几次了。
先来看看这些基本接口吧:
1 /// <summary> 2 /// 软删除接口 3 /// </summary> 4 public interface ILogicallyDeletable 5 { 6 /// <summary> 7 /// 逻辑删除标记 8 /// </summary> 9 bool IsDeleted { get; set; } 10 } 11 12 /// <summary> 13 /// 活动状态标记接口 14 /// </summary> 15 public interface IActiveControllable 16 { 17 /// <summary> 18 /// 活动状态标记 19 /// </summary> 20 bool? Active { get; set; } 21 } 22 23 /// <summary> 24 /// 乐观并发接口 25 /// </summary> 26 public interface IOptimisticConcurrencySupported 27 { 28 /// <summary> 29 /// 行版本,乐观并发锁 30 /// </summary> 31 [ConcurrencyCheck] 32 string ConcurrencyStamp { get; set; } 33 } 34 35 /// <summary> 36 /// 插入顺序记录接口 37 /// </summary> 38 public interface IStorageOrderRecordable 39 { 40 /// <summary> 41 /// 非自增顺序字段作为主键类型 42 /// 应该在此列建立聚集索引避免随机的字段值导致数据库索引性能下降 43 /// 同时保存数据插入先后的信息 44 /// </summary> 45 long InsertOrder { get; set; } 46 } 47 48 /// <summary> 49 /// 创建时间记录接口 50 /// </summary> 51 public interface ICreationTimeRecordable 52 { 53 /// <summary> 54 /// 实体创建时间 55 /// </summary> 56 DateTimeOffset CreationTime { get; set; } 57 } 58 59 /// <summary> 60 /// 最后修改时间记录接口 61 /// </summary> 62 public interface ILastModificationTimeRecordable 63 { 64 /// <summary> 65 /// 最后一次修改时间 66 /// </summary> 67 DateTimeOffset LastModificationTime { get; set; } 68 } 69 70 /// <summary> 71 /// 创建人id记录接口 72 /// </summary> 73 /// <typeparam name="TIdentityKey">创建人主键类型</typeparam> 74 public interface ICreatorRecordable<TIdentityKey> 75 where TIdentityKey : struct, IEquatable<TIdentityKey> 76 { 77 /// <summary> 78 /// 创建人Id 79 /// </summary> 80 TIdentityKey? CreatorId { get; set; } 81 } 82 83 /// <summary> 84 /// 创建人记录接口 85 /// </summary> 86 /// <typeparam name="TIdentityKey">创建人主键类型</typeparam> 87 /// <typeparam name="TIdentityUser">创建人类型</typeparam> 88 public interface ICreatorRecordable<TIdentityKey, TIdentityUser> : ICreatorRecordable<TIdentityKey> 89 where TIdentityKey : struct , IEquatable<TIdentityKey> 90 where TIdentityUser : IEntity<TIdentityKey> 91 { 92 /// <summary> 93 /// 创建人 94 /// </summary> 95 TIdentityUser Creator { get; set; } 96 } 97 98 /// <summary> 99 /// 上次修改人id记录接口 100 /// </summary> 101 /// <typeparam name="TIdentityKey">上次修改人主键类型</typeparam> 102 public interface ILastModifierRecordable<TIdentityKey> 103 where TIdentityKey : struct, IEquatable<TIdentityKey> 104 { 105 /// <summary> 106 /// 上一次修改人Id 107 /// </summary> 108 TIdentityKey? LastModifierId { get; set; } 109 } 110 111 /// <summary> 112 /// 上次修改人记录接口 113 /// </summary> 114 /// <typeparam name="TIdentityKey">上次修改人主键类型</typeparam> 115 /// <typeparam name="TIdentityUser">上次修改人类型</typeparam> 116 public interface ILastModifierRecordable<TIdentityKey, TIdentityUser> : ILastModifierRecordable<TIdentityKey> 117 where TIdentityKey : struct, IEquatable<TIdentityKey> 118 where TIdentityUser : IEntity<TIdentityKey> 119 { 120 /// <summary> 121 /// 上一次修改人 122 /// </summary> 123 TIdentityUser LastModifier { get; set; } 124 }
这些基本接口每一个都对应了一个基本功能。还有一个稍微复杂的树形数据结构接口:
1 /// <summary> 2 /// 树形数据接口 3 /// </summary> 4 /// <typeparam name="T">节点数据类型</typeparam> 5 public interface ITree<T> 6 { 7 /// <summary> 8 /// 父节点 9 /// </summary> 10 T Parent { get; set; } 11 12 /// <summary> 13 /// 子节点集合 14 /// </summary> 15 IList<T> Children { get; set; } 16 17 /// <summary> 18 /// 节点深度,根的深度为0 19 /// </summary> 20 int Depth { get; } 21 22 /// <summary> 23 /// 是否是根节点 24 /// </summary> 25 bool IsRoot { get; } 26 27 /// <summary> 28 /// 是否是叶节点 29 /// </summary> 30 bool IsLeaf { get; } 31 32 /// <summary> 33 /// 是否有子节点 34 /// </summary> 35 bool HasChildren { get; } 36 37 /// <summary> 38 /// 节点路径(UNIX路径格式,以“/”分隔) 39 /// </summary> 40 string Path { get; } 41 }
然后是打包接口,主要是把基本接口打包到一个统一接口,方便批量使用:
1 /// <summary> 2 /// 实体接口 3 /// </summary> 4 public interface IEntity {} 5 6 /// <summary> 7 /// 泛型实体接口,约束Id属性 8 /// </summary> 9 public interface IEntity<TKey> : IEntity 10 where TKey : IEquatable<TKey> 11 { 12 TKey Id { get; set; } 13 } 14 15 /// <summary> 16 /// 领域实体接口,主要是整合各个小接口 17 /// </summary> 18 public interface IDomainEntity : IEntity 19 , ILogicallyDeletable 20 , ICreationTimeRecordable 21 , ILastModificationTimeRecordable 22 , INotifyPropertyChanged 23 , INotifyPropertyChangedExtension 24 , IPropertyChangeTrackable 25 {} 26 27 /// <summary> 28 /// 泛型领域实体接口 29 /// </summary> 30 public interface IDomainEntity<TKey> : IEntity<TKey> 31 , IDomainEntity 32 where TKey : struct, IEquatable<TKey> 33 {}
树形数据结构也有一套:
1 /// <summary> 2 /// 树形实体接口 3 /// </summary> 4 /// <typeparam name="T">实体类型</typeparam> 5 public interface ITreeEntity<T> : IEntity, ITree<T> 6 { 7 } 8 9 /// <summary> 10 /// 树形实体接口 11 /// </summary> 12 /// <typeparam name="TKey">主键类型</typeparam> 13 /// <typeparam name="TEntity">实体类型</typeparam> 14 public interface ITreeEntity<TKey, TEntity> : ITreeEntity<TEntity>, IEntity<TKey> 15 where TKey : IEquatable<TKey> 16 where TEntity : ITreeEntity<TKey, TEntity> 17 { 18 } 19 20 /// <summary> 21 /// 树形领域实体接口 22 /// </summary> 23 /// <typeparam name="T">数据类型</typeparam> 24 public interface IDomainTreeEntity<T> : 25 IDomainEntity 26 , ITreeEntity<T> 27 { 28 } 29 30 /// <summary> 31 /// 树形领域实体接口 32 /// </summary> 33 /// <typeparam name="TKey">主键类型</typeparam> 34 /// <typeparam name="TEntity">树形实体类型</typeparam> 35 public interface IDomainTreeEntity<TKey, TEntity> : 36 IDomainTreeEntity<TEntity> 37 , IDomainEntity<TKey> 38 , ITreeEntity<TKey, TEntity> 39 40 where TKey : struct, IEquatable<TKey> 41 where TEntity : IDomainTreeEntity<TKey, TEntity> 42 { 43 TKey? ParentId { get; set; } 44 }
最后还有几个特别用处的接口:
1 /// <summary> 2 /// 跟踪属性的变更 3 /// </summary> 4 public interface IPropertyChangeTrackable 5 { 6 /// <summary> 7 /// 判断指定的属性或任意属性是否被变更过 8 /// </summary> 9 /// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性</param> 10 /// <returns> 11 /// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False)</para> 12 /// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)</para> 13 /// </returns> 14 bool HasChanges(params string[] names); 15 16 /// <summary> 17 /// 获取实体中发生过变更的属性集 18 /// </summary> 19 /// <returns>如果实体没有属性发生过变更,则返回空白字典,否则返回被变更过的属性键值对</returns> 20 IDictionary<string, object> GetChanges(); 21 22 /// <summary> 23 /// 重置指定的属性或任意属性变更状态(为未变更) 24 /// </summary> 25 /// <param name="names">指定要重置的属性名数组,如果为空(null)或空数组则表示重置所有属性的变更状态(为未变更)</param> 26 void ResetPropertyChangeStatus(params string[] names); 27 } 28 29 /// <summary> 30 /// 多对多导航实体接口 31 /// </summary> 32 /// <typeparam name="TIdentityKey">身份实体主键类型</typeparam> 33 /// <typeparam name="TIdentityUser">身份实体类型</typeparam> 34 public interface IManyToManyReferenceEntity<TIdentityKey, TIdentityUser> : IManyToManyReferenceEntity<TIdentityKey> 35 , ICreatorRecordable<TIdentityKey, TIdentityUser> 36 where TIdentityKey : struct, IEquatable<TIdentityKey> 37 where TIdentityUser : IEntity<TIdentityKey> 38 { 39 } 40 41 /// <summary> 42 /// 多对多导航实体接口 43 /// </summary> 44 /// <typeparam name="TIdentityKey">身份实体主键类型</typeparam> 45 public interface IManyToManyReferenceEntity<TIdentityKey> : IManyToManyReferenceEntity 46 , ICreatorRecordable<TIdentityKey> 47 where TIdentityKey : struct, IEquatable<TIdentityKey> 48 { 49 } 50 51 /// <summary> 52 /// 多对多导航实体接口 53 /// </summary> 54 public interface IManyToManyReferenceEntity : IEntity 55 , ICreationTimeRecordable 56 { 57 }
至此,基本上用到的接口就定义好了,接下来就是魔改 Identity 实体类,这里以 IdentityRole 为例,其他的可以到我的项目中查看,大同小异:
1 public class ApplicationRole : ApplicationRole<int, ApplicationUser, ApplicationRole, ApplicationUserRole, ApplicationRoleClaim> 2 , IStorageOrderRecordable 3 { 4 public ApplicationRole() { } 5 public ApplicationRole(string roleName) => Name = roleName; 6 7 public virtual long InsertOrder { get; set; } 8 } 9 10 public abstract class ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim> : IdentityRole<TKey> 11 , IDomainTreeEntity<TKey, TIdentityRole> 12 , IOptimisticConcurrencySupported 13 , ICreatorRecordable<TKey, TIdentityUser> 14 , ILastModifierRecordable<TKey, TIdentityUser> 15 where TKey : struct, IEquatable<TKey> 16 where TIdentityUser : IEntity<TKey> 17 where TUserRole : ApplicationUserRole<TKey, TIdentityUser, TIdentityRole> 18 where TRoleClaim : ApplicationRoleClaim<TKey, TIdentityUser, TIdentityRole> 19 where TIdentityRole : ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim> 20 { 21 #region 重写基类属性使属性变更通知事件生效 22 23 public override TKey Id { get => base.Id; set => base.Id = value; } 24 public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; } 25 public override string Name { get => base.Name; set => base.Name = value; } 26 public override string NormalizedName { get => base.NormalizedName; set => base.NormalizedName = value; } 27 28 #endregion 29 30 public string Description { get; set; } 31 32 /// <summary> 33 /// 需要使用.Include(r => r.UserRoles).ThenInclude(ur => ur.Role)预加载或启用延迟加载 34 /// </summary> 35 [NotMapped] 36 public virtual IEnumerable<TIdentityUser> Users => UserRoles?.Select(ur => ur.User); 37 38 #region 导航属性 39 40 public virtual List<TUserRole> UserRoles { get; set; } = new List<TUserRole>(); 41 42 public virtual List<TRoleClaim> RoleClaims { get; set; } = new List<TRoleClaim>(); 43 44 #endregion 45 46 #region IDomainTreeEntity成员 47 48 public virtual TKey? ParentId { get; set; } 49 50 #endregion 51 52 #region IEntity成员 53 54 public virtual bool? Active { get; set; } = true; 55 public virtual bool IsDeleted { get; set; } 56 public virtual DateTimeOffset CreationTime { get; set; } = DateTimeOffset.Now; 57 public virtual DateTimeOffset LastModificationTime { get; set; } = DateTimeOffset.Now; 58 59 #endregion 60 61 #region IDomainEntity成员 62 63 public virtual TKey? CreatorId { get; set; } 64 public virtual TIdentityUser Creator { get; set; } 65 public virtual TKey? LastModifierId { get; set; } 66 public virtual TIdentityUser LastModifier { get; set; } 67 68 #endregion 69 70 #region ITree成员 71 72 public virtual TIdentityRole Parent { get; set; } 73 74 public virtual IList<TIdentityRole> Children { get; set; } 75 76 [DoNotNotify, NotMapped] 77 public virtual int Depth => Parent?.Depth + 1 ?? 0; 78 79 [DoNotNotify, NotMapped] 80 public virtual bool IsRoot => Parent == null; 81 82 [DoNotNotify, NotMapped] 83 public virtual bool IsLeaf => Children?.Count == 0; 84 85 [DoNotNotify, NotMapped] 86 public virtual bool HasChildren => !IsLeaf; 87 88 [DoNotNotify, NotMapped] 89 public virtual string Path => Parent == null ? Id.ToString() : $@"{Parent.Path}/{Id}"; 90 91 #endregion 92 93 #region IPropertyChangeTrackable成员 94 95 private static readonly object Locker = new object(); 96 private static readonly Dictionary<Type, string[]> PropertyNamesDictionary = new Dictionary<Type, string[]>(); 97 98 private readonly BitArray _propertyChangeMask; 99 100 /// <summary> 101 /// 全局属性变更通知事件处理器 102 /// </summary> 103 public static PropertyChangedEventHandler PublicPropertyChangedEventHandler { get; set; } 104 105 /// <summary> 106 /// 初始化用于跟踪属性变更所需的属性信息 107 /// </summary> 108 protected ApplicationRole() 109 { 110 //判断类型是否已经加入字典 111 //将未加入的类型添加进去(一般为该类对象首次初始化时) 112 var type = this.GetType(); 113 if (!PropertyNamesDictionary.ContainsKey(type)) 114 { 115 lock (Locker) 116 { 117 if (!PropertyNamesDictionary.ContainsKey(type)) 118 { 119 PropertyNamesDictionary.Add(type, type.GetProperties() 120 .OrderBy(property => property.Name) 121 .Select(property => property.Name).ToArray()); 122 } 123 } 124 } 125 126 //初始化属性变更掩码 127 _propertyChangeMask = new BitArray(PropertyNamesDictionary[type].Length, false); 128 129 //注册全局属性变更事件处理器 130 if (PublicPropertyChangedEventHandler != null) 131 { 132 PropertyChanged += PublicPropertyChangedEventHandler; 133 } 134 } 135 136 /// <summary> 137 /// 属性变更事件 138 /// </summary> 139 public event PropertyChangedEventHandler PropertyChanged; 140 public event PropertyChangedExtensionEventHandler PropertyChangedExtension; 141 142 /// <summary> 143 /// 内部属性变更事件处理器 144 /// </summary> 145 /// <param name="propertyName">属性名</param> 146 /// <param name="oldValue">旧值</param> 147 /// <param name="newValue">新值</param> 148 protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue) 149 { 150 //Perform property validation 151 152 _propertyChangeMask[Array.IndexOf(PropertyNamesDictionary[this.GetType()], propertyName)] = true; 153 154 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 155 PropertyChangedExtension?.Invoke(this, new PropertyChangedExtensionEventArgs(propertyName, oldValue, newValue)); 156 } 157 158 /// <summary> 159 /// 判断指定的属性或任意属性是否被变更过(<see cref="IPropertyChangeTrackable"/>接口的实现) 160 /// </summary> 161 /// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性。</param> 162 /// <returns> 163 /// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False);</para> 164 /// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)。</para> 165 /// </returns> 166 public bool HasChanges(params string[] names) 167 { 168 if (!(names?.Length > 0)) 169 { 170 foreach (bool mask in _propertyChangeMask) 171 { 172 if (mask == true) 173 { 174 return true; 175 } 176 } 177 178 return false; 179 } 180 181 var type = this.GetType(); 182 foreach (var name in names) 183 { 184 var index = Array.IndexOf(PropertyNamesDictionary[type], name); 185 if (index >= 0 && _propertyChangeMask[index] == true) 186 { 187 return true; 188 } 189 } 190 191 return false; 192 } 193 194 /// <summary> 195 /// 获取实体中发生过变更的属性集(<see cref="IPropertyChangeTrackable"/>接口的实现) 196 /// </summary> 197 /// <returns>如果实体没有属性发生过变更,则返回空白字典,否则返回被变更过的属性键值对</returns> 198 public IDictionary<string, object> GetChanges() 199 { 200 Dictionary<string, object> changeDictionary = new Dictionary<string, object>(); 201 var type = this.GetType(); 202 for (int i = 0; i < _propertyChangeMask.Length; i++) 203 { 204 if (_propertyChangeMask[i] == true) 205 { 206 changeDictionary.Add(PropertyNamesDictionary[type][i], 207 type.GetProperty(PropertyNamesDictionary[type][i])?.GetValue(this)); 208 } 209 } 210 211 return changeDictionary; 212 } 213 214 /// <summary> 215 /// 重置指定的属性或任意属性变更状态(为未变更)(<see cref="IPropertyChangeTrackable"/>接口的实现) 216 /// </summary> 217 /// <param name="names">指定要重置的属性名数组,如果为空(null)或空数组则表示重置所有属性的变更状态(为未变更)</param> 218 public void ResetPropertyChangeStatus(params string[] names) 219 { 220 if (names?.Length > 0) 221 { 222 var type = this.GetType(); 223 foreach (var name in names) 224 { 225 var index = Array.IndexOf(PropertyNamesDictionary[type], name); 226 if (index >= 0) 227 { 228 _propertyChangeMask[index] = false; 229 } 230 } 231 } 232 else 233 { 234 _propertyChangeMask.SetAll(false); 235 } 236 } 237 238 #endregion 239 }
可以看到我在为 IdentityRole 添加接口实现的时候添加的是 IDomainTreeEntity 接口。在这里我把 Role 改成了树形数据类型,也就是说一个角色可以是另一个角色的子角色,构成树状关系。当然如果就当作普通的 Role 来使用也没有任何问题,这个扩展完全不会破坏任何内置功能,没有任何侵入性,按需选用就好,至于能发挥什么作用,完全看脑洞有多大 (●'◡'●)
然而,这还不是全部,不然就对不起魔改的名号了。现在看见的代码还不是最终形态。因为使用了 PropertyChanged.Fody 这个库,所有的实体都可以向外发送属性变更通知,至于能发挥什么作用,还是看脑洞。
代码最终形态预览(此处使用了 ILSpy 反编译引擎的 Nuget 包,详情见我之前的博客C# 编译器 和 反编译器,你要哪个(歪头)? 我全都要(捏拳)!):
魔改部分还不止这些,但是和我接下来打算介绍的部分存在重叠,所以剩下的部分就和接下来的介绍放在一起了,会新开一篇博客。
各位观众老爷对我的魔改实体类有什么感想欢迎评论交流。可以到下方我的 Github 存储库下载项目运行体验效果。
转载请完整保留以下内容并在显眼位置标注,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!
本文地址:
完整源代码:Github
里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。
加载全部内容