【问题标题】:Base entity common to all tables in EF Core 3.0EF Core 3.0 中所有表共有的基本实体
【发布时间】:2021-05-17 20:10:56
【问题描述】:

我想使用所有实体通用的基本实体,因为每个表都应该有广告 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; }
}

在我开始添加关系之前一切都很好。现在,在添加了与外键的关系之后,迁移只创建了一个名为 BaseEntity 和鉴别器的大表,但抽象基实体只能用于继承公共属性。

我读过here 有 3 种继承类型,但在 EF Core 3.0 中只有 TPH 可用。在网上看到带有抽象基类的例子没有这个问题。

我想知道我的实现中是否遗漏了一些东西,请大家帮我找出答案。

【问题讨论】:

  • 您是否声明了 BaseEntity 类型的 Navigaion 属性?如果你这样做了,BaseEntity 将成为一个实体并获得它自己的表,你不应该这样做。 BaseEntity 类型的 Navigation 属性也有可怕的代码气味。
  • 我有来自 BaseEntity 的派生类型的导航属性,但不是直接属于 BaseEntity 类型的导航属性。例如,我有一个从 BaseEntity 扩展的 A 类,并且具有 A 类型的导航属性。这是问题吗?
  • 应该没问题。尝试简化模型并生成一个简单的独立重现并将其发布在您的问题中。
  • 我发现了问题。当我使用 Fluent API 为 InsertDateTime 和 LastModifiedDateTime 设置默认值时会发生这种情况:modelBuilder.Entity<BaseEntity>().Property(x => x.DateTimeInserimento).HasDefaultValueSql("getdate()");modelBuilder.Entity<BaseEntity>().Property(x => x.DateTimeUltimaModifica).HasDefaultValueSql("getdate()"); 但我需要这个数据库设置来自动生成这个值。

标签: sql-server asp.net-core entity-framework-core ef-code-first


【解决方案1】:

这个:

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 中的某种触发器或拦截器。

【讨论】:

  • 是的,完全正确!它就像一个魅力。非常感谢。现在我将致力于更新 LastModifiedDateTime。
【解决方案2】:

这是另一种选择。

    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");
    }
}

最后,对 db 上下文的添加处理更新的日期/时间和更新的日期/时间。

    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
}

【讨论】:

  • 谢谢pjs,我去试试!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-30
  • 1970-01-01
  • 2020-04-09
  • 2022-10-18
  • 1970-01-01
  • 2022-11-30
相关资源
最近更新 更多