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

avatar
SeriousM
1 июля 2021 в 15:49
197
1
0

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

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

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

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

...

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)

avatar
SeriousM
2 июля 2021 в 13:48
0

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

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.