如何解决EF Core 3.0 中所有表通用的基本实体
我想使用所有实体通用的基本实体,因为每个表都应该有广告 ID、InsertDate 和 LastModifiedDate。
根据文档,我应该创建一个 BaseEntity 抽象类,并且每个实体都应该继承它。
public abstract class BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime? InsertDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? LastModifiedDateTime { get; set; }
}
一切正常,直到我开始添加关系。现在,在添加了与外键的关系之后,Migration 只创建了一个名为 BaseEntity 的大表,带有鉴别器,但抽象基实体应该只用于继承公共属性。
我读过 here 有 3 种类型的继承,但在 EF Core 3.0 中只有 TPH 可用。在网上看到抽象基类的例子没有这个问题。
我想知道我的实现中是否遗漏了一些东西,请大家帮我找出来。
解决方法
这个:
modelBuilder.Entity<BaseEntity>()
将 BaseEntity 声明为数据库实体。而是配置所有子类型。由于它们被映射到单独的表,它们需要单独的配置。 EG
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Ignore<BaseEntity>();
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
if (et.ClrType.IsSubclassOf(typeof(BaseEntity)))
{
et.FindProperty("InsertDateTime").SetDefaultValueSql("getdate()");
et.FindProperty("InsertDateTime").ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAdd;
et.FindProperty("LastModifiedDateTime").SetDefaultValueSql("getdate()");
et.FindProperty("LastModifiedDateTime").ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAddOrUpdate;
}
}
base.OnModelCreating(modelBuilder);
}
请注意,这不会导致每次更改都会更新 LastModifiedDateTime
。这将需要 EF 中的某种触发器或拦截器。
这是另一种选择。
public abstract class BaseEntityWithUpdatedAndRowVersion
{
[Display(Name = "Updated By",Description = "User who last updated this meeting.")]
[Editable(false)]
[ScaffoldColumn(false)]
public string UpdatedBy { get; set; }
[Display(Name = "Updated",Description = "Date and time this row was last updated.")]
[Editable(false)]
[ScaffoldColumn(false)]
public DateTimeOffset UpdatedDateTime { get; set; }
[Display(Name = "SQL Server Timestamp",ShortName = "RowVersion",Description = "Internal SQL Server row version stamp.")]
[Timestamp]
[Editable(false)]
[ScaffoldColumn(false)]
public byte[] RowVersion { get; set; }
}
}
及其抽象配置:
internal abstract class BaseEntityWithUpdatedAndRowVersionConfiguration <TBase> : IEntityTypeConfiguration<TBase>
where TBase: BaseEntityWithUpdatedAndRowVersion
{
public virtual void Configure(EntityTypeBuilder<TBase> entity)
{
entity.Property(e => e.UpdatedBy)
.IsRequired()
.HasMaxLength(256);
entity.Property(e => e.UpdatedDateTime)
.HasColumnType("datetimeoffset(0)")
.HasDefaultValueSql("(sysdatetimeoffset())");
entity.Property(e => e.RowVersion)
.IsRequired()
.IsRowVersion();
}
}
这是一个使用基本实体的具体类。
public partial class Invitation: BaseEntityWithUpdatedAndRowVersion,IValidatableObject
{
[Display(Name = "Paper",Description = "Paper being invited.")]
[Required]
public int PaperId { get; set; }
[Display(Name = "Full Registration Fee Waived?",ShortName = "Fee Waived?",Description = "Is the registration fee completely waived for this author?")]
[Required]
public bool IsRegistrationFeeFullyWaived { get; set; }
}
及其配置代码,调用基础配置:
internal class InvitationConfiguration : BaseEntityWithUpdatedAndRowVersionConfiguration<Invitation>
{
public override void Configure(EntityTypeBuilder<Invitation> entity)
{
base.Configure(entity);
entity.HasKey(e => e.PaperId);
entity.ToTable("Invitations","Offerings");
entity.Property(e => e.PaperId).ValueGeneratedNever();
entity.HasOne(d => d.Paper)
.WithOne(p => p.Invitation)
.HasForeignKey<Invitation>(d => d.PaperId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("Invitations_FK_IsFor_Paper");
}
}
最后,这个添加到数据库上下文处理更新日期/时间。
public partial class ConferenceDbContext : IdentityDbContext<ConferenceUser,ConferenceRole,int>
{
public override int SaveChanges()
{
AssignUpdatedByAndTime();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
AssignUpdatedByAndTime();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
AssignUpdatedByAndTime();
return await base.SaveChangesAsync(cancellationToken).ConfigureAwait(true);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,CancellationToken cancellationToken = default)
{
AssignUpdatedByAndTime();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess,cancellationToken).ConfigureAwait(true);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization","CA1303:Do not pass literals as localized parameters",Justification = "App is not being globalized.")]
private void AssignUpdatedByAndTime()
{
//Get changed entities (added or modified).
ChangeTracker.DetectChanges();
var changedEntities = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)
.ToList();
//Assign UpdatedDateTime and UpdatedBy properties if they exist.
AssignUpdatedByUserAndTime(changedEntities);
}
#region AssignUpdated
/// <summary>Assign updated-by & updated-when to any entity containing those attributes,including general category parent entities.</summary>
private void AssignUpdatedByUserAndTime(List<EntityEntry> changedEntities)
{
foreach (EntityEntry entityEntry in changedEntities)
{
//Some subcategory entities have the updated date/by attributes in the parent entity.
EntityEntry parentEntry = null;
string entityTypeName = entityEntry.Metadata.Name; //Full class name,e.g.,ConferenceEF.Models.Meeting
entityTypeName = entityTypeName.Split('.').Last();
switch (entityTypeName)
{
case "Paper":
case "FlashPresentation":
case "Break":
parentEntry = entityEntry.Reference(nameof(SessionItem)).TargetEntry;
break;
default:
break;
}
AssignUpdatedByUserAndTime(parentEntry ?? entityEntry);
}
}
private void AssignUpdatedByUserAndTime(EntityEntry entityEntry)
{
if (entityEntry.Entity is BaseEntityWithUpdatedAndRowVersion
|| entityEntry.Entity is PaperRating)
{
PropertyEntry updatedDateTime = entityEntry.Property("UpdatedDateTime");
DateTimeOffset? currentValue = (DateTimeOffset?)updatedDateTime.CurrentValue;
//Avoid possible loops by only updating time when it has changed by at least 1 minute.
//Is this necessary?
if (!currentValue.HasValue || currentValue < DateTimeOffset.Now.AddMinutes(-1))
updatedDateTime.CurrentValue = DateTimeOffset.Now;
if (entityEntry.Properties.Any(p => p.Metadata.Name == "UpdatedBy"))
{
PropertyEntry updatedBy = entityEntry.Property("UpdatedBy");
string newValue = CurrentUserName; //ClaimsPrincipal.Current?.Identity?.Name;
if (newValue == null && !updatedBy.Metadata.IsColumnNullable())
newValue = string.Empty;
if (updatedBy.CurrentValue?.ToString() != newValue)
updatedBy.CurrentValue = newValue;
}
}
}
#endregion AssignUpdated
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。