首页 > 解决方案 > 级联删除在运行时使用 EF Core 配置的模型

问题描述

为了实现一个基本的修订历史系统,我定义了一些Action实体,这些实体配置了反射并包含在 EF Core 的模型构建器中。每个动作都被视为一个单独的实体,并包含对应实体的外键。

例如:

public interface IAction
{
  public long Id { get; }
  public long DateTime Date { get; }
  public long CreatorId { get; }
  public User Creator { get; }
}

public interface IPostAction
{
  long PostId { get; }
  Post Post { get; }
}

public interface IUserAction
{
  long UserId { get; }
  User User { get; }
}

public abstract class ActionBase : IAction
{
  public long Id { get; set; }
  public DateTime Date { get; set; }
  public long CreatorId { get; set; }
  [ForeignKey( nameof( CreatorId ) )]
  public User Creator { get; set; }
}

public abstract class PostAction : ActionBase, IPostAction
{
  public long PostId { get; set; }
  [ForeignKey( nameof( PostId ) )]
  public Post Post { get; set; }
}

public class AddPostFollowerAction : PostAction, IUserAction
{
  public long UserId { get; set; }
  [ForeignKey( nameof( UserId ) )]
  public User User { get; set; }
}

然后通过创建DynamicAssembly为每种类型定义 IEntityTypeConfiguration 的方法来配置这些模型。

/// <inheritdoc />
   public abstract class ActionConfiguration<TAction> : IEntityTypeConfiguration<TAction>
      where TAction : class, IAction
   {

      /// <inheritdoc />
      /// <summary>
      /// Configures the <see cref="IAction" />.
      /// </summary>
      /// <param name="builder">The configuration builder.</param>
      public void Configure( EntityTypeBuilder<TAction> builder )
      {
         builder.HasKey( a => a.Id );
         builder.ToTable( $"Actions_{typeof(TAction).Name}" );

         builder.HasOne( a => a.Creator )
                .WithMany()
                .HasForeignKey( a => a.CreatorId );
      }

      /// <summary>
      /// Configures the <see cref="IEntity" /> implementation.
      /// </summary>
      /// <param name="builder">
      /// The configuration builder.
      /// </param>
      public virtual void Configuring( EntityTypeBuilder<TAction> builder )
      {
      }

   }

   /// <summary>
   /// A static cache that auto-generates entity configurations for 
   /// concrete <see cref="IAction"/> classes
   /// </summary>
   public static class ActionConfigurationCache
   {

      #region Data Members

      /// <summary>
      /// The dynamic assembly that stores the auto-generated
      /// <see cref="ActionConfiguration{TAction}"/>s.
      /// </summary>
      public static readonly Assembly Assembly;

      #endregion

      #region Constructor

      /// <summary>
      /// Initializes the cache.
      /// </summary>
      static ActionConfigurationCache()
      {
         Assembly = CreateConfigurationAssembly();
      }

      #endregion

      #region Private Methods

      /// <summary>
      /// Creates a dynamic assembly that stores auto-generated
      /// <see cref="ActionConfiguration{TAction}"/>s.
      /// </summary>
      /// <returns>
      /// The dynamic assembly.
      /// </returns>
      private static Assembly CreateConfigurationAssembly()
      {
         // Gather concrete Action types
         var actionTypes = typeof( IAction ).GetConcreteTypes();

         // Create Dynamic Assembly
         var assemblyName = new AssemblyName( "ActionConfigurationAssembly" );
         var assembly = AssemblyBuilder.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run );

         // Create Dynamic Module
         var module = assembly.DefineDynamicModule( "ActionConfigurationCache" );

         // Create ActionConfiguration types
         foreach( var actionType in actionTypes )
            CreateConfigurationClass( module, actionType );

         // Build Assembly
         return assembly;
      }

      /// <summary>
      /// Creates an entity configuration for the specified <see cref="IAction"/> type
      /// </summary>
      /// <param name="module">
      /// The <see cref="Module"/> to define the entity configuration in.
      /// </param>
      /// <param name="type">
      /// A concrete <see cref="IAction"/> implementation to generate an
      /// entity configuration for.
      /// </param>
      /// <returns>
      /// The <see cref="TypeInfo"/> of the generated entity configuration.
      /// </returns>
      private static TypeInfo CreateConfigurationClass( ModuleBuilder module, Type type )
      {
         // Define concrete ActionConfiguration type
         var configGeneric = typeof( ActionConfiguration<> ).MakeGenericType( type );
         var configType = module.DefineType(
            $"{type.Name}Configuration",
            TypeAttributes.Class,
            configGeneric );

         // Create the type.
         configType.CreateTypeInfo();
         return configType;
      }

      #endregion

   }

在删除实体之前,这种方法可以正常工作。如果帖子被删除,由于导航实体在类中的设置方式,它会引发外键错误Action。由于他们使用ForeignKeyAttributeAddPostFollowerAction被指定为从属实体,并被Post指定为主体实体。因此,当尝试级联删除时,由于角色本质上是切换的,因此级联删除不会删除任何Action实体并导致违反约束。

由于Action将要定义的实体的数量(以及由于特性蠕变而在未来添加的更多实体)以及 EntityFramework 不以这种方式支持多态性的事实,因此Action在其对应的实体中为每个个体定义导航属性并不是真的可行的。

有没有办法可以配置级联删除以自动删除这些操作?

标签: c#sql-serverentity-framework-coreef-core-2.2

解决方案


推荐阅读