Нужны отношения многие ко многим для нескольких свойств на одном объекте EF Core 2.2 - PullRequest
1 голос
/ 30 мая 2019

У меня есть объект для Users и объект для Projects.

Мне нужно иметь возможность назначить нескольким пользователям 3 разных свойства списка в моем объекте проекта.Я был в состоянии сделать это успешно для одного свойства (отношения многие ко многим) сущностью соединения.Я мог бы указать UserType в таблице «Пользователи» и просто использовать одно свойство, но я могу столкнуться со сценариями, в которых пользователи могут выполнять более одной роли (типа), и тогда это не сработает.

IЯ подумал, что могу просто поместить UserType в таблицу соединений (сущность), но я в растерянности относительно того, как создать эту сущность в моем DBContext.

Вот то, что у меня есть, это работает с одним свойствомопределены:

ProjectEntity:

public class Project : IInt32Identity
{
    public int Id { get; set; }
    public string ProjectName { get; set; }
    ...
    public bool ProjectActive { get; set; }
    public List<ProjectFile> ProjectFiles { get; set; }
    public List<ProjectUsers> ProjectUsers { get; set; }
    public DateTime ProjectCreatedDate { get; set; }
    public DateTime ProjectModifiedDate { get; set; }
}

UserEntity:

public class User : IInt32Identity
{
    public int Id { get; set; }
    public string UserEmail { get; set; }
    ...
    public List<ProjectUsers> ProjectUsers { get; set; }
    public DateTime UserCreatedDate { get; set; }
    public DateTime UserLastLoggedInDate { get; set; }
    public DateTime UserModifiedDate { get; set; }
}

JoinEntity:

public class ProjectUsers
{
    public int UserId { get; set; }
    public User User { get; set; }
    public int ProjectId { get; set; }
    public Project Project { get; set; }
}

И мой OnModelCreating() в моем DBContext

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ProjectUsers>()
            .HasKey(bc => new { bc.UserId, bc.ProjectId });
        modelBuilder.Entity<ProjectUsers>()
            .HasOne(bc => bc.User)
            .WithMany(b => b.ProjectUsers)
            .HasForeignKey(bc => bc.UserId);
        modelBuilder.Entity<ProjectUsers>()
            .HasOne(bc => bc.Project)
            .WithMany(c => c.ProjectUsers)
            .HasForeignKey(bc => bc.ProjectId);
    }

Все работает отлично, как я уже говорил выше, но вот что я хотел бы:

ProjectEntity:

public class Project : IInt32Identity
{
    public int Id { get; set; }
    public string ProjectName { get; set; }
    ...
    public bool ProjectActive { get; set; }
    public List<ProjectFile> ProjectFiles { get; set; }
    public List<ProjectUsers> ProjectClients { get; set; }
    public List<ProjectUsers> ProjectBuilders { get; set; }
    public List<ProjectUsers> ProjectDesigners { get; set; }
    public DateTime ProjectCreatedDate { get; set; }
    public DateTime ProjectModifiedDate { get; set; }
}

UserEntity - то же самое.

JoinEntity:

public class ProjectUsers
{
    public int UserId { get; set; }
    public User User { get; set; }
    public int ProjectId { get; set; }
    public Project Project { get; set; }
    public string UserType { get; set; }
}

Я потерян в коде OnModelBinding(), и я также не уверен, что EF будет достаточно умен для правильного заполнения списков на основе мета UserTypeсобственность.

Буду признателен за любую помощь или руководство.

TIA

1 Ответ

2 голосов
/ 30 мая 2019

Может показаться возможным обработать ProjectUser как базовый класс / сущность и создать другой класс / сущность / тип для ProjectClient, ProjectBuilder и ProjectDesigner, которые унаследованы от ProjectUser. А затем вы создаете таблицы для каждого типа и отношение один-ко-многим к проекту. Обычно это называется подход таблицы к типу (TPT) .

Однако TPT еще не реализован в EF Core.

Вы все еще можете достичь этого, используя Таблица на иерархию (TPH) , но у вас будет только один список в проекте для всех пользователей проекта, где UserId, ProjectId и UserType станут сложный ключ. Клиенты проекта, строители и дизайнеры будут рассчитывать свойства из этого одного списка пользователей проекта.

Сущность

public class Project
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<ProjectUser> ProjectUsers { get; set; }

    public IEnumerable<ProjectUser> ProjectClients => this.ProjectUsers
        .Where(x => x.UserType == "Client");
    public IEnumerable<ProjectUser> ProjectBuilders => this.ProjectUsers
        .Where(x => x.UserType == "Builder");
    public IEnumerable<ProjectUser> ProjectDesigners => this.ProjectUsers
        .Where(x => x.UserType == "Designer");
}

public class User
{
    public int Id { get; set; }
    public string Email { get; set; }

    public virtual ICollection<ProjectUser> UserProjects { get; set; }
}

public class ProjectUser
{
    public int UserId { get; set; }
    public virtual User User { get; set; }

    public int ProjectId { get; set; }
    public virtual Project Project { get; set; }

    public string UserType { get; set; }
}

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

public class ProjectConfiguration : IEntityTypeConfiguration<Project>
{
    public void Configure(EntityTypeBuilder<Project> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).IsRequired();
        builder.HasIndex(x => x.Name).IsUnique();

        builder.Ignore(x => x.ProjectBuilders);
        builder.Ignore(x => x.ProjectClients);
        builder.Ignore(x => x.ProjectDesigners);

        builder.ToTable("Project");
    }
}

public class UserConfiguration : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Email).IsRequired();
        builder.HasIndex(x => x.Email).IsUnique();

        builder.ToTable("User");
    }
}

public class ProjectUserConfiguration : IEntityTypeConfiguration<ProjectUser>
{
    public void Configure(EntityTypeBuilder<ProjectUser> builder)
    {
        builder.HasKey(x => new { x.ProjectId, x.UserId, x.UserType });
        builder.Property(x => x.UserType).IsRequired();

        builder.HasOne(x => x.Project)
            .WithMany(x => x.ProjectUsers)
            .HasForeignKey(x => x.ProjectId);

        builder.HasOne(x => x.User)
            .WithMany(x => x.UserProjects)
            .HasForeignKey(x => x.UserId);
    }
}

Ключевое слово virtual используется для поддержки отложенной загрузки. Если вы не выполняете отложенную загрузку, вам не нужно иметь virtual там. Кроме того, вам необходимо [NotMapped] эти 3 рассчитанных свойства, что аналогично использованию .Ignore в общедоступном API.

DbContext

public class AppDbContext : DbContext
{
    public DbSet<Project> Projects { get; set; }
    public DbSet<User> Users { get; set; }

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

        modelBuilder.ApplyConfiguration(new ProjectConfiguration());
        modelBuilder.ApplyConfiguration(new UserConfiguration());
        modelBuilder.ApplyConfiguration(new ProjectUserConfiguration());
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder
            .UseLazyLoadingProxies()
            .UseSqlServer("Data Source=.\\SQLEXPRESS;Initial Catalog=DL.SO.ProjectUsersDemo;Integrated Security=True;MultipleActiveResultSets=False;");
    }
}

Ничего особенного здесь. После того, как вы добавите миграцию и обновите базу данных, ваша должна выглядеть как

enter image description here

После заполнения базы данных примерами данных, хотя здесь это трудно показать, вы можете видеть, что эти 3 списка заполнены правильными данными:

enter image description here

...