前言

默认的 Identity 实体类型在大多数时候已经基本够用,很多时候也只是稍微在 IdentityUser 类中增加一些自定义数据字段,比如头像。这次,我要向园友隆重介绍我魔改之后的 Identity 实体类,能支持一些特别风骚的操作。当然也完全兼容内置的 UserManager、RoleManager 和 SignInManager,毕竟也是从内置类型继承扩展出来的。

正文

魔改的实体类基于一组我自定义实体接口,这组接口我也实现了一组打包好的基础类型。因为 Identity 系列实体类型已经存在,而 C# 不支持多重继承,所以只能把这些代码在魔改的 Identity 实体类中粘贴几次了。

先来看看这些基本接口吧:

     /// <summary>
/// 软删除接口
/// </summary>
public interface ILogicallyDeletable
{
/// <summary>
/// 逻辑删除标记
/// </summary>
bool IsDeleted { get; set; }
} /// <summary>
/// 活动状态标记接口
/// </summary>
public interface IActiveControllable
{
/// <summary>
/// 活动状态标记
/// </summary>
bool? Active { get; set; }
} /// <summary>
/// 乐观并发接口
/// </summary>
public interface IOptimisticConcurrencySupported
{
/// <summary>
/// 行版本,乐观并发锁
/// </summary>
[ConcurrencyCheck]
string ConcurrencyStamp { get; set; }
} /// <summary>
/// 插入顺序记录接口
/// </summary>
public interface IStorageOrderRecordable
{
/// <summary>
/// 非自增顺序字段作为主键类型
/// 应该在此列建立聚集索引避免随机的字段值导致数据库索引性能下降
/// 同时保存数据插入先后的信息
/// </summary>
long InsertOrder { get; set; }
} /// <summary>
/// 创建时间记录接口
/// </summary>
public interface ICreationTimeRecordable
{
/// <summary>
/// 实体创建时间
/// </summary>
DateTimeOffset CreationTime { get; set; }
} /// <summary>
/// 最后修改时间记录接口
/// </summary>
public interface ILastModificationTimeRecordable
{
/// <summary>
/// 最后一次修改时间
/// </summary>
DateTimeOffset LastModificationTime { get; set; }
} /// <summary>
/// 创建人id记录接口
/// </summary>
/// <typeparam name="TIdentityKey">创建人主键类型</typeparam>
public interface ICreatorRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
{
/// <summary>
/// 创建人Id
/// </summary>
TIdentityKey? CreatorId { get; set; }
} /// <summary>
/// 创建人记录接口
/// </summary>
/// <typeparam name="TIdentityKey">创建人主键类型</typeparam>
/// <typeparam name="TIdentityUser">创建人类型</typeparam>
public interface ICreatorRecordable<TIdentityKey, TIdentityUser> : ICreatorRecordable<TIdentityKey>
where TIdentityKey : struct , IEquatable<TIdentityKey>
where TIdentityUser : IEntity<TIdentityKey>
{
/// <summary>
/// 创建人
/// </summary>
TIdentityUser Creator { get; set; }
} /// <summary>
/// 上次修改人id记录接口
/// </summary>
/// <typeparam name="TIdentityKey">上次修改人主键类型</typeparam>
public interface ILastModifierRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
{
/// <summary>
/// 上一次修改人Id
/// </summary>
TIdentityKey? LastModifierId { get; set; }
} /// <summary>
/// 上次修改人记录接口
/// </summary>
/// <typeparam name="TIdentityKey">上次修改人主键类型</typeparam>
/// <typeparam name="TIdentityUser">上次修改人类型</typeparam>
public interface ILastModifierRecordable<TIdentityKey, TIdentityUser> : ILastModifierRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
where TIdentityUser : IEntity<TIdentityKey>
{
/// <summary>
/// 上一次修改人
/// </summary>
TIdentityUser LastModifier { get; set; }
}

这些基本接口每一个都对应了一个基本功能。还有一个稍微复杂的树形数据结构接口:

     /// <summary>
/// 树形数据接口
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
public interface ITree<T>
{
/// <summary>
/// 父节点
/// </summary>
T Parent { get; set; } /// <summary>
/// 子节点集合
/// </summary>
IList<T> Children { get; set; } /// <summary>
/// 节点深度,根的深度为0
/// </summary>
int Depth { get; } /// <summary>
/// 是否是根节点
/// </summary>
bool IsRoot { get; } /// <summary>
/// 是否是叶节点
/// </summary>
bool IsLeaf { get; } /// <summary>
/// 是否有子节点
/// </summary>
bool HasChildren { get; } /// <summary>
/// 节点路径(UNIX路径格式,以“/”分隔)
/// </summary>
string Path { get; }
}

然后是打包接口,主要是把基本接口打包到一个统一接口,方便批量使用:

     /// <summary>
/// 实体接口
/// </summary>
public interface IEntity {} /// <summary>
/// 泛型实体接口,约束Id属性
/// </summary>
public interface IEntity<TKey> : IEntity
where TKey : IEquatable<TKey>
{
TKey Id { get; set; }
} /// <summary>
/// 领域实体接口,主要是整合各个小接口
/// </summary>
public interface IDomainEntity : IEntity
, ILogicallyDeletable
, ICreationTimeRecordable
, ILastModificationTimeRecordable
, INotifyPropertyChanged
, INotifyPropertyChangedExtension
, IPropertyChangeTrackable
{} /// <summary>
/// 泛型领域实体接口
/// </summary>
public interface IDomainEntity<TKey> : IEntity<TKey>
, IDomainEntity
where TKey : struct, IEquatable<TKey>
{}

树形数据结构也有一套:

     /// <summary>
/// 树形实体接口
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
public interface ITreeEntity<T> : IEntity, ITree<T>
{
} /// <summary>
/// 树形实体接口
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TEntity">实体类型</typeparam>
public interface ITreeEntity<TKey, TEntity> : ITreeEntity<TEntity>, IEntity<TKey>
where TKey : IEquatable<TKey>
where TEntity : ITreeEntity<TKey, TEntity>
{
} /// <summary>
/// 树形领域实体接口
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public interface IDomainTreeEntity<T> :
IDomainEntity
, ITreeEntity<T>
{
} /// <summary>
/// 树形领域实体接口
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TEntity">树形实体类型</typeparam>
public interface IDomainTreeEntity<TKey, TEntity> :
IDomainTreeEntity<TEntity>
, IDomainEntity<TKey>
, ITreeEntity<TKey, TEntity> where TKey : struct, IEquatable<TKey>
where TEntity : IDomainTreeEntity<TKey, TEntity>
{
TKey? ParentId { get; set; }
}

最后还有几个特别用处的接口:

     /// <summary>
/// 跟踪属性的变更
/// </summary>
public interface IPropertyChangeTrackable
{
/// <summary>
/// 判断指定的属性或任意属性是否被变更过
/// </summary>
/// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性</param>
/// <returns>
/// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False)</para>
/// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)</para>
/// </returns>
bool HasChanges(params string[] names); /// <summary>
/// 获取实体中发生过变更的属性集
/// </summary>
/// <returns>如果实体没有属性发生过变更,则返回空白字典,否则返回被变更过的属性键值对</returns>
IDictionary<string, object> GetChanges(); /// <summary>
/// 重置指定的属性或任意属性变更状态(为未变更)
/// </summary>
/// <param name="names">指定要重置的属性名数组,如果为空(null)或空数组则表示重置所有属性的变更状态(为未变更)</param>
void ResetPropertyChangeStatus(params string[] names);
} /// <summary>
/// 多对多导航实体接口
/// </summary>
/// <typeparam name="TIdentityKey">身份实体主键类型</typeparam>
/// <typeparam name="TIdentityUser">身份实体类型</typeparam>
public interface IManyToManyReferenceEntity<TIdentityKey, TIdentityUser> : IManyToManyReferenceEntity<TIdentityKey>
, ICreatorRecordable<TIdentityKey, TIdentityUser>
where TIdentityKey : struct, IEquatable<TIdentityKey>
where TIdentityUser : IEntity<TIdentityKey>
{
} /// <summary>
/// 多对多导航实体接口
/// </summary>
/// <typeparam name="TIdentityKey">身份实体主键类型</typeparam>
public interface IManyToManyReferenceEntity<TIdentityKey> : IManyToManyReferenceEntity
, ICreatorRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
{
} /// <summary>
/// 多对多导航实体接口
/// </summary>
public interface IManyToManyReferenceEntity : IEntity
, ICreationTimeRecordable
{
}

至此,基本上用到的接口就定义好了,接下来就是魔改 Identity 实体类,这里以 IdentityRole 为例,其他的可以到我的项目中查看,大同小异:

     public class ApplicationRole : ApplicationRole<int, ApplicationUser, ApplicationRole, ApplicationUserRole, ApplicationRoleClaim>
, IStorageOrderRecordable
{
public ApplicationRole() { }
public ApplicationRole(string roleName) => Name = roleName; public virtual long InsertOrder { get; set; }
} public abstract class ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim> : IdentityRole<TKey>
, IDomainTreeEntity<TKey, TIdentityRole>
, IOptimisticConcurrencySupported
, ICreatorRecordable<TKey, TIdentityUser>
, ILastModifierRecordable<TKey, TIdentityUser>
where TKey : struct, IEquatable<TKey>
where TIdentityUser : IEntity<TKey>
where TUserRole : ApplicationUserRole<TKey, TIdentityUser, TIdentityRole>
where TRoleClaim : ApplicationRoleClaim<TKey, TIdentityUser, TIdentityRole>
where TIdentityRole : ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim>
{
#region 重写基类属性使属性变更通知事件生效 public override TKey Id { get => base.Id; set => base.Id = value; }
public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; }
public override string Name { get => base.Name; set => base.Name = value; }
public override string NormalizedName { get => base.NormalizedName; set => base.NormalizedName = value; } #endregion public string Description { get; set; } /// <summary>
/// 需要使用.Include(r => r.UserRoles).ThenInclude(ur => ur.Role)预加载或启用延迟加载
/// </summary>
[NotMapped]
public virtual IEnumerable<TIdentityUser> Users => UserRoles?.Select(ur => ur.User); #region 导航属性 public virtual List<TUserRole> UserRoles { get; set; } = new List<TUserRole>(); public virtual List<TRoleClaim> RoleClaims { get; set; } = new List<TRoleClaim>(); #endregion #region IDomainTreeEntity成员 public virtual TKey? ParentId { get; set; } #endregion #region IEntity成员 public virtual bool? Active { get; set; } = true;
public virtual bool IsDeleted { get; set; }
public virtual DateTimeOffset CreationTime { get; set; } = DateTimeOffset.Now;
public virtual DateTimeOffset LastModificationTime { get; set; } = DateTimeOffset.Now; #endregion #region IDomainEntity成员 public virtual TKey? CreatorId { get; set; }
public virtual TIdentityUser Creator { get; set; }
public virtual TKey? LastModifierId { get; set; }
public virtual TIdentityUser LastModifier { get; set; } #endregion #region ITree成员 public virtual TIdentityRole Parent { get; set; } public virtual IList<TIdentityRole> Children { get; set; } [DoNotNotify, NotMapped]
public virtual int Depth => Parent?.Depth + ?? ; [DoNotNotify, NotMapped]
public virtual bool IsRoot => Parent == null; [DoNotNotify, NotMapped]
public virtual bool IsLeaf => Children?.Count == ; [DoNotNotify, NotMapped]
public virtual bool HasChildren => !IsLeaf; [DoNotNotify, NotMapped]
public virtual string Path => Parent == null ? Id.ToString() : $@"{Parent.Path}/{Id}"; #endregion #region IPropertyChangeTrackable成员 private static readonly object Locker = new object();
private static readonly Dictionary<Type, string[]> PropertyNamesDictionary = new Dictionary<Type, string[]>(); private readonly BitArray _propertyChangeMask; /// <summary>
/// 全局属性变更通知事件处理器
/// </summary>
public static PropertyChangedEventHandler PublicPropertyChangedEventHandler { get; set; } /// <summary>
/// 初始化用于跟踪属性变更所需的属性信息
/// </summary>
protected ApplicationRole()
{
//判断类型是否已经加入字典
//将未加入的类型添加进去(一般为该类对象首次初始化时)
var type = this.GetType();
if (!PropertyNamesDictionary.ContainsKey(type))
{
lock (Locker)
{
if (!PropertyNamesDictionary.ContainsKey(type))
{
PropertyNamesDictionary.Add(type, type.GetProperties()
.OrderBy(property => property.Name)
.Select(property => property.Name).ToArray());
}
}
} //初始化属性变更掩码
_propertyChangeMask = new BitArray(PropertyNamesDictionary[type].Length, false); //注册全局属性变更事件处理器
if (PublicPropertyChangedEventHandler != null)
{
PropertyChanged += PublicPropertyChangedEventHandler;
}
} /// <summary>
/// 属性变更事件
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedExtensionEventHandler PropertyChangedExtension; /// <summary>
/// 内部属性变更事件处理器
/// </summary>
/// <param name="propertyName">属性名</param>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue)
{
//Perform property validation _propertyChangeMask[Array.IndexOf(PropertyNamesDictionary[this.GetType()], propertyName)] = true; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
PropertyChangedExtension?.Invoke(this, new PropertyChangedExtensionEventArgs(propertyName, oldValue, newValue));
} /// <summary>
/// 判断指定的属性或任意属性是否被变更过(<see cref="IPropertyChangeTrackable"/>接口的实现)
/// </summary>
/// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性。</param>
/// <returns>
/// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False);</para>
/// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)。</para>
/// </returns>
public bool HasChanges(params string[] names)
{
if (!(names?.Length > ))
{
foreach (bool mask in _propertyChangeMask)
{
if (mask == true)
{
return true;
}
} return false;
} var type = this.GetType();
foreach (var name in names)
{
var index = Array.IndexOf(PropertyNamesDictionary[type], name);
if (index >= && _propertyChangeMask[index] == true)
{
return true;
}
} return false;
} /// <summary>
/// 获取实体中发生过变更的属性集(<see cref="IPropertyChangeTrackable"/>接口的实现)
/// </summary>
/// <returns>如果实体没有属性发生过变更,则返回空白字典,否则返回被变更过的属性键值对</returns>
public IDictionary<string, object> GetChanges()
{
Dictionary<string, object> changeDictionary = new Dictionary<string, object>();
var type = this.GetType();
for (int i = ; i < _propertyChangeMask.Length; i++)
{
if (_propertyChangeMask[i] == true)
{
changeDictionary.Add(PropertyNamesDictionary[type][i],
type.GetProperty(PropertyNamesDictionary[type][i])?.GetValue(this));
}
} return changeDictionary;
} /// <summary>
/// 重置指定的属性或任意属性变更状态(为未变更)(<see cref="IPropertyChangeTrackable"/>接口的实现)
/// </summary>
/// <param name="names">指定要重置的属性名数组,如果为空(null)或空数组则表示重置所有属性的变更状态(为未变更)</param>
public void ResetPropertyChangeStatus(params string[] names)
{
if (names?.Length > )
{
var type = this.GetType();
foreach (var name in names)
{
var index = Array.IndexOf(PropertyNamesDictionary[type], name);
if (index >= )
{
_propertyChangeMask[index] = false;
}
}
}
else
{
_propertyChangeMask.SetAll(false);
}
} #endregion
}

可以看到我在为 IdentityRole 添加接口实现的时候添加的是 IDomainTreeEntity 接口。在这里我把 Role 改成了树形数据类型,也就是说一个角色可以是另一个角色的子角色,构成树状关系。当然如果就当作普通的 Role 来使用也没有任何问题,这个扩展完全不会破坏任何内置功能,没有任何侵入性,按需选用就好,至于能发挥什么作用,完全看脑洞有多大 (●'◡'●)

然而,这还不是全部,不然就对不起魔改的名号了。现在看见的代码还不是最终形态。因为使用了 PropertyChanged.Fody 这个库,所有的实体都可以向外发送属性变更通知,至于能发挥什么作用,还是看脑洞。

代码最终形态预览(此处使用了 ILSpy 反编译引擎的 Nuget 包,详情见我之前的博客C# 编译器 和 反编译器,你要哪个(歪头)? 我全都要(捏拳)!):

Asp.Net Core Identity 骚断腿的究极魔改实体类-LMLPHP

魔改部分还不止这些,但是和我接下来打算介绍的部分存在重叠,所以剩下的部分就和接下来的介绍放在一起了,会新开一篇博客。

各位观众老爷对我的魔改实体类有什么感想欢迎评论交流。可以到下方我的 Github 存储库下载项目运行体验效果。

转载请完整保留以下内容并在显眼位置标注,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!

  本文地址:https://www.cnblogs.com/coredx/p/12310010.html

  完整源代码:Github

  里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

05-28 08:00