Есть ли хороший шаблон для реализации абстрактного DbContext в EF7 / .Net Core 3, чтобы избежать дублирования общих сущностей / конфигурации между проектами? - PullRequest
3 голосов
/ 19 сентября 2019

У меня есть несколько различных проектов, которые реализуют одну и ту же схему для конфигурации, безопасности и аудита и ищут шаблон, который позволил бы мне поместить эти определения схемы в абстрактные классы (entity, configuration и dbcontext), которые могутбыть расширенным в конкретных реализациях, если это необходимо.Мой текущий POC терпит неудачу, когда применяются базовые конфигурации.Я получаю:

Ключ не может быть настроен на UserRole, потому что это производный тип.Ключ должен быть настроен для корневого типа.

Любая помощь / указатели будут с благодарностью!

У меня есть следующие примеры кода ....

Абстрактные базовые классы

RoleBase

    public abstract class RoleBase
    {
        public RoleBase()
        {
            this.UserRoles = new List<UserRoles>();
        }

        public long Id { get; set; }       
        public string Name { get; set; }
        public virtual IEnumerable<UserRoleBase> UserRoles { get; set; }
    }

UserBase

    public abstract class UserBase
    {
        public long Id { get; set; } 
        public string Username { get; set; }
        public string Email { get; set; }
        public virtual ICollection<UserRoleBase> UserRoles { get; set; }
    }

UserRoleBase

    public abstract class UserRoleBase
    {
        public long Id { get; set; } 
        public long RoleId { get; set; }
        public long UserId { get; set; }
        public bool Deleted { get; set; }
        public virtual RoleBase Role { get; set; }
        public virtual UserBase User { get; set; }
    }

Каждый из них имеет абстрактный класс конфигурации для базового класса ...

Конфигурация RoleBase

public abstract class RoleConfiguration<T> : IEntityTypeConfiguration<T>
        where T : RoleBase
    {
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

            // Properties
            builder.Property(t => t.Name)
                .IsRequired()
                .HasMaxLength(50);

            // Table & Column Mappings
            builder.ToTable("Role", "Security");
            builder.Property(t => t.Id).HasColumnName("Id");
            builder.Property(t => t.Name).HasColumnName("Name");
        }
    }

Конфигурация UserBase

    public abstract class UserConfiguration<TBase> : IEntityTypeConfiguration<TBase>
        where TBase : UserBase
    {
        public virtual void Configure(EntityTypeBuilder<TBase> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

            // Properties
            builder.Property(t => t.Username).IsRequired().HasMaxLength(255);
            builder.Property(t => t.Email).IsRequired().HasMaxLength(255);

            // Table & Column Mappings
            builder.ToTable("User", "Security");
            builder.Property(t => t.Id).HasColumnName("Id");
            builder.Property(t => t.Username).HasColumnName("Username");
            builder.Property(t => t.Email).HasColumnName("Email");
        }
    }

Конфигурация UserRoleBase

    public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
        where T : UserRoleBase
    {
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

            // Properties
            builder.Property(t => t.RoleId).IsRequired();
            builder.Property(t => t.UserId).IsRequired();
            builder.Property(t => t.Deleted).IsRequired();

            // Table & Column Mappings
            builder.ToTable("UserRole", "Security");
            builder.Property(t => t.Id).HasColumnName("Id");
            builder.Property(t => t.RoleId).HasColumnName("RoleId");
            builder.Property(t => t.UserId).HasColumnName("UserId");
            builder.Property(t => t.Deleted).HasColumnName("Deleted");

            // Relationships
            builder.HasOne(t => t.Role)
                .WithMany(t => (ICollection<TBase>)t.UserRoles)
                .HasForeignKey(d => d.RoleId)
                .OnDelete(DeleteBehavior.Restrict);

            builder.HasOne(t => t.UserDetail)
                .WithMany(t => (ICollection<TBase>)t.UserRoles)
                .HasForeignKey(d => d.UserDetailId)
                .OnDelete(DeleteBehavior.Restrict);
        }

И конкретная реализация базовых классов:

Роль

    public class Role : RoleBase
    {

    }

Пользователь

    public class User : UserBase
    {
        // Extension properties
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Phone { get; set; }
        public string Mobile { get; set; }
    }

UserRole

    public class UserRole : UserRoleBase
    {

    }

И конкретная реализация конфигурации

RoleConfiguration

    public class RoleConfiguration : Base.Configurations.RoleConfiguration<Role>
    {
        public override void Configure(EntityTypeBuilder<Role> builder)
        {
            base.Configure(builder);
            this.ConfigureEntity(builder);
        }

        private void ConfigureEntity(EntityTypeBuilder<Role> builder)
        {
        }
    }

UserConfiguration

    public class UserConfiguration : Base.Configurations.UserConfiguration<User>
    {
        public override void Configure(EntityTypeBuilder<User> builder)
        {
            base.Configure(builder);
            this.ConfigureEntity(builder);
        }

        private void ConfigureEntity(EntityTypeBuilder<User> builder)
        {
            //Registration of extension properties
            builder.Property(t => t.FirstName).HasColumnName("FirstName");
            builder.Property(t => t.LastName).HasColumnName("LastName");
            builder.Property(t => t.Phone).HasColumnName("Phone");
            builder.Property(t => t.Mobile).HasColumnName("Mobile");
        }
    }

UserRoleConfiguration

    public class UserRoleConfiguration : Base.Configurations.UserRoleConfiguration<UserRole>
    {
        public override void Configure(EntityTypeBuilder<UserRole> builder)
        {
            base.Configure(builder);
            this.ConfigureEntity(builder);
        }

        private void ConfigureEntity(EntityTypeBuilder<UserRole> builder)
        {
        }
    }

И базовый контекст

public abstract class BaseDbContext: DbContext
    {

        public BaseDbContext(DbContextOptions<BaseDbContext> options)
            : base(options)
        {

        }

        // https://github.com/aspnet/EntityFramework.Docs/issues/594
        protected BaseDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<RoleBase> Roles { get; set; }
        public DbSet<UserBase> Users { get; set; }
        public DbSet<UserRoleBase> UserRoles { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {           
            base.OnModelCreating(modelBuilder);
        }
    }

И конкретный контекст

public class MyDbContext: BaseDbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options)
        :base(options)
        {
        }

        protected MyDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public new DbSet<Role> Roles { get; set; }
        public new DbSet<User> Users { get; set; }
        public new DbSet<UserRole> UserRoles { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new RoleConfiguration());
            modelBuilder.ApplyConfiguration(new UserConfiguration());
            modelBuilder.ApplyConfiguration(new UserRoleConfiguration());

            base.OnModelCreating(modelBuilder);
        }
    }

Таким образом, все это работает для элементов, которые не имеют свойств навигации и прекрасно переносятся в базу данных, пока нет свойств навигации.Я вижу, как выполняются свойства расширения для пользователя, пока я закомментирую все свойства навигации.

При наличии свойств навигации я получаю сообщение об ошибке в классе базовой конфигурации.после конкретной реализации под названием base.Configure (builder);

я получаю следующее сообщение об ошибке на builder.HasKey (t => t.Id); и для приведенного выше примера кода это будетбыть на ...

    public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
        where T : UserRoleBase
    {
        public virtual void Configure(EntityTypeBuilder<T> builder)
        {
            // Primary Key
            builder.HasKey(t => t.Id);

System.InvalidOperationException: 'Ключ не может быть настроен на' UserRole ', потому что это производный тип.Ключ должен быть настроен для корневого типа «UserRoleBase».Если вы не собирались включать в модель «UserRoleBase», убедитесь, что он не включен в свойство DbSet вашего контекста, не указан в вызове конфигурации для ModelBuilder или не указан в свойстве навигации для включенного типав модели. '

Есть ли способ, которым я могу сохранить эти реляционные конфигурации в абстрактном базовом классе, чтобы мне не нужно было повторять их в каждой конкретной реализации базовых классов?Или есть другой подход, который можно использовать для преодоления этой проблемы?

1 Ответ

1 голос
/ 20 сентября 2019

System.InvalidOperationException: «Невозможно настроить ключ для« UserRole », поскольку он является производным типом.Ключ должен быть настроен для корневого типа «UserRoleBase».Если вы не собирались включать в модель «UserRoleBase», убедитесь, что он не включен в свойство DbSet вашего контекста, не указан в вызове конфигурации для ModelBuilder или не указан в свойстве навигации для включенного типав модели. '

Из-за ошибки вы можете использовать атрибут Key для идентификатора базовой модели, чтобы указать первичный ключ.

Из-за критических изменений, включенных в EF Core 3.0, ToTable для производного типа вызывает исключение , в настоящее время недопустимо отображать производный тип в другую таблицу.Это изменение позволяет избежать разрыва в будущем, когда оно становится действительным.

Вы можете использовать аннотации данных в базовой модели для настройки таблицы, с которой сопоставляется тип:

[Table("Role", Schema = "Security")]
public abstract class RoleBase
{
    public RoleBase()
    {
        this.UserRoles = new List<UserRoles>();
    }
    [Key]
    public long Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<UserRoleBase> UserRoles { get; set; }
}
...