前言 預設的 Identity 實體類型在大多數時候已經基本夠用,很多時候也只是稍微在 IdentityUser 類中增加一些自定義數據欄位,比如頭像。這次,我要向園友隆重介紹我魔改之後的 Identity 實體類,能支持一些特別風騷的操作。當然也完全相容內置的 UserManager、RoleMa ...
前言
預設的 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 }View Code
這些基本介面每一個都對應了一個基本功能。還有一個稍微複雜的樹形數據結構介面:
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 }View Code
然後是打包介面,主要是把基本介面打包到一個統一介面,方便批量使用:
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 {}View Code
樹形數據結構也有一套:
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 }View Code
最後還有幾個特別用處的介面:
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 }View Code
至此,基本上用到的介面就定義好了,接下來就是魔改 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 /// 初始化用於跟蹤屬性變更所需的屬性信息