Ядро Entity Framework: Множество отношений «Один ко многим» между двумя сущностями

#c# #entity-framework-core

Вопрос:

Я пытаюсь запустить модель перевода, имея

 public class Item
{
  public Item
  {
      TextTranslationID = Guid.NewGuid();
      DescriptionTranslationID = Guid.NewGuid();

      TextTranslations = new HashSet<Translation>();
      DescriptionTranslations = new HashSet<Translation>();
  }

  [Key]
  public int ItemID { get; set; }

  public Guid TextTranslationID { get; set; }

  public Guid DescriptionTranslationID { get; set; }

  [ForeignKey(nameof(TextTranslationID))]
  public virtual ICollection<Translation> TextTranslations { get; set; }

  [ForeignKey(nameof(DescriptionTranslationID))]
  public virtual ICollection<Translation> DescriptionTranslations { get; set; }
}
 

и организация перевода

 public class Translation
{
  public Translation()
  {
    UniqueTranslationID = Guid.NewGuid();
  }

  [Key]
  public Guid UniqueTranslationID { get; set; }

  /// <summary>
  /// The translation id, keyed with the language.
  /// </summary>
  [Required]
  public Guid TranslationID { get; set; }

  /// <summary>
  /// The 2-char language code. eg "en", "es"
  /// </summary>
  [Required]
  [StringLength(2, MinimumLength = 2)]
  public string Language { get; set; }

  [Required]
  [StringLength(2000)]
  public string Text { get; set; }
}
 

Это отношения в одном направлении, поэтому мне не нужен и не нужен перевод.Родитель или что — то подобное в сущности перевода.
Item Сущность является лишь одним из многих потребителей перевода, поэтому здесь не требуется обратное свойство.
Как вы можете видеть, этот элемент имеет две связи с переводами.

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

 ...

migrationBuilder.CreateTable(
    name: "Translations",
    columns: table => new
    {
        UniqueTranslationID = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        TranslationID = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
        Language = table.Column<string>(type: "nvarchar(2)", maxLength: 2, nullable: false),
        Text = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
        DescriptionTranslationID = table.Column<int>(type: "int", nullable: true),
        TextTranslationID = table.Column<int>(type: "int", nullable: true)
    },

...
 

Как я могу настроить два отношения «один ко многим» от элемента к переводу?
Большое спасибо!

Ответ №1:

Вот полный пример с решением.
Даны две сущности (Вопрос, ответ), которым требуется несколько локализаций (Локализация) без необходимости обратной навигации.

 public class CatalogQuestion
{
  public CatalogQuestion()
  {
    TextTranslationID = Guid.NewGuid();
    DescriptionTranslationID = Guid.NewGuid();

    TextTranslations = new HashSet<Translation>();
    DescriptionTranslations = new HashSet<Translation>();
  }

  [Key]
  public int CatalogQuestionID { get; set; }
  public Guid TextTranslationID { get; set; }
  public Guid DescriptionTranslationID { get; set; }

  ///

  public virtual ICollection<Translation> TextTranslations { get; set; }

  public virtual ICollection<Translation> DescriptionTranslations { get; set; }
}

public class CatalogAnswer
{
  public CatalogAnswer()
  {
    TextTranslationID = Guid.NewGuid();
    DescriptionTranslationID = Guid.NewGuid();

    TextTranslations = new HashSet<Translation>();
    DescriptionTranslations = new HashSet<Translation>();
  }

  [Key]
  public int CatalogAnswerID { get; set; }
  public Guid TextTranslationID { get; set; }
  public Guid DescriptionTranslationID { get; set; }
  
  ///

  public virtual ICollection<Translation> TextTranslations { get; set; }

  public virtual ICollection<Translation> DescriptionTranslations { get; set; }
}

/// <summary>
/// Allows every entity that has a <see cref="Guid"/> to have a translation.
/// </summary>
public class Translation
{
  public Translation()
  {
    UniqueTranslationID = Guid.NewGuid();
  }

  /// <summary>
  /// Used for direct addressing this single translation.
  /// </summary>
  [Key]
  public Guid UniqueTranslationID { get; set; }

  /// <summary>
  /// The translation id, keyed with the language.
  /// Must not be an empty guid.
  /// </summary>
  [Required]
  public Guid TranslationID { get; set; }

  /// <summary>
  /// The 2-char language code. eg "de", "en"
  /// </summary>
  [Required]
  [StringLength(2, MinimumLength = 2)]
  public string Language { get; set; }
  
  [Required]
  [StringLength(2000)]
  public string Text { get; set; }
}
 

Это FluentAPI для сборщика моделей, который склеивает все вместе:

 protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Translation>(entity => {
      entity.HasIndex(e => new { e.TranslationID, e.Language }).IsUnique();
    });

    modelBuilder.Entity<CatalogQuestion>(entity => {
      entity.HasMany(e => e.TextTranslations).WithOne().HasForeignKey(e => e.TranslationID).HasPrincipalKey(e => e.TextTranslationID).HasConstraintName("FK_Translations_CatalogQuestionsText_TranslationID");
      entity.HasMany(e => e.DescriptionTranslations).WithOne().HasForeignKey(e => e.TranslationID).HasPrincipalKey(e => e.DescriptionTranslationID).HasConstraintName("FK_Translations_CatalogQuestionsDescription_TranslationID");
    });

    modelBuilder.Entity<CatalogAnswer>(entity => {
      entity.HasMany(e => e.TextTranslations).WithOne().HasForeignKey(e => e.TranslationID).HasPrincipalKey(e => e.TextTranslationID).HasConstraintName("FK_Translations_CatalogAnswersText_TranslationID");
      entity.HasMany(e => e.DescriptionTranslations).WithOne().HasForeignKey(e => e.TranslationID).HasPrincipalKey(e => e.DescriptionTranslationID).HasConstraintName("FK_Translations_CatalogAnswersDescription_TranslationID");
    });
}
 

Поскольку мы создаем связь между двумя не-припарными ключами, нам нужно определить ее в FlientApi, иначе миграция будет спутана с аннотациями и FluentAPI.