Отношения один-к-одному с EF Core - PullRequest
0 голосов
/ 18 февраля 2020

То, чего я хочу достичь, - это иметь модель CommentsThread, которую можно прикрепить к любой другой модели, требующей комментариев, будь то Chapter, BlogPost, UserProfile или какая у вас. По сути, мне нужна структура

CommentsThread
  int ID

Chapter
  int ID
  int Thread FK(CommentsThread.ID)

BlogPost
  int ID
  int Thread FK(CommentsThread.ID)

UserProfile
  int ID
  int Thread FK(CommentsThread.ID)

, однако я понятия не имею, как правильно настроить ее в ядре EF, без необходимости добавлять в Nuller обнуляемые ссылки на Chapter, BlogPost и UserProfile. CommentsThread модель.

Мой код для CommentsThread выглядит следующим образом:

public class CommentsThread
{
    [Key]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public ICollection<Comment> Comments { get; set; }
}

И Chapter, без каких-либо не относящихся к делу свойств, составляет

public class Chapter
{
    [Key]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public CommentsThread CommentsThread { get; set; }
}

Со следующей конфигурацией Fluent (опять же, пропуская ненужные биты):

builder.Entity<CommentsThread>()
    .HasMany(ct => ct.Comments)
    .WithOne()
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<Chapter>()
    .HasOne(c => c.CommentsThread)
    .WithOne()
    .OnDelete(DeleteBehavior.Cascade);

При dotnet ef migrations add X он выбрасывает этот вывод в консоль.


Обновление:

Итак, я попытался явно добавить public int CommentsThreadId { get; set; } к Chapter и изменить конфигурацию на

builder.Entity<Chapter>()
    .HasOne(c => c.CommentsThread)
    .WithOne()
    .HasForeignKey<Chapter>(c => c.CommentsThreadId)
    .OnDelete(DeleteBehavior.Cascade);

Это действительно сделало миграцию go через, но не удалось на database update с

The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.Chapters' and the index name 'IX_Chapters_CommentsThreadId'. The duplicate key value is (0).
The statement has been terminated.

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Ошибка переноса вашей базы данных;

The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.Chapters' and the index name 'IX_Chapters_CommentsThreadId'. The duplicate key value is (0).
The statement has been terminated.

Произошло из-за добавления ненулевого столбца в таблицу, в которой уже есть данные. Если вы хотите, чтобы CommentsThread существовал для каждого типа записи, вам также нужно написать сценарий миграции, чтобы вставить эти записи для существующих данных.

Или просто измените тип столбца внешнего ключа на int?, так что разрешит нули.

1 голос
/ 18 февраля 2020

AFAIK, существует два способа настройки отношения 1-1:

  1. https://www.entityframeworktutorial.net/efcore/one-to-one-conventions-entity-framework-core.aspx
  2. https://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt

1-й пост требует ссылки от каждой модели. но вы упомянули:

без добавления пустых ссылок

тогда только 2-я концепция (таблица на тип) может достичь вашей цели, но пост слишком старый код для EF Core.

Вот новые модели данных:

public class CommentsThread
{
    [Key]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    public string Title { get; set; }

    public ICollection<ThreadComment> Comments { get; set; }
}

[Table(nameof(Chapter))]
public class Chapter
{
    [Key]
    [Required]
    [ForeignKey(nameof(CommentsThread))]
    public int Id { get; set; }

    public int Number { get; set; }

    public CommentsThread CommentsThread { get; set; }
}
[Table(nameof(BlogPost))]
public class BlogPost
{
    [Key]
    [Required]
    [ForeignKey(nameof(CommentsThread))]
    public int Id { get; set; }

    public string Author { get; set; }

    public CommentsThread CommentsThread { get; set; }
}

[Table(nameof(UserProfile))]
public class UserProfile
{
    [Key]
    [Required]
    [ForeignKey(nameof(CommentsThread))]
    public int Id { get; set; }
    public string Bio { get; set; }

    public CommentsThread CommentsThread { get; set; }
}

public class ThreadComment
{
    [Key]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    public string Content { get; set; }

    public CommentsThread CommentsThread { get; set; }
}

Вот пример контекста:

public class ApplicationDbContext : IdentityDbContext
{
    public virtual DbSet<CommentsThread> CommentsThreads { get; set; }
    public virtual DbSet<Chapter> Chapters { get; set; }
    public virtual DbSet<BlogPost> BlogPosts { get; set; }
    public virtual DbSet<UserProfile> UserProfiles { get; set; }
    public virtual DbSet<ThreadComment> ThreadComments { get; set; }

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }


    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Chapter>().ToTable(nameof(Chapter));
        modelBuilder.Entity<BlogPost>().ToTable(nameof(BlogPost));
        modelBuilder.Entity<UserProfile>().ToTable(nameof(UserProfile));
    }
}

Вот сгенерированный код миграции:

public partial class AddCommentsThread : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "CommentsThreads",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Title = table.Column<string>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_CommentsThreads", x => x.Id);
            });

        migrationBuilder.CreateTable(
            name: "BlogPost",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false),
                Author = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_BlogPost", x => x.Id);
                table.ForeignKey(
                    name: "FK_BlogPost_CommentsThreads_Id",
                    column: x => x.Id,
                    principalTable: "CommentsThreads",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateTable(
            name: "Chapter",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false),
                Number = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Chapter", x => x.Id);
                table.ForeignKey(
                    name: "FK_Chapter_CommentsThreads_Id",
                    column: x => x.Id,
                    principalTable: "CommentsThreads",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateTable(
            name: "ThreadComments",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Content = table.Column<string>(nullable: false),
                CommentsThreadId = table.Column<int>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_ThreadComments", x => x.Id);
                table.ForeignKey(
                    name: "FK_ThreadComments_CommentsThreads_CommentsThreadId",
                    column: x => x.CommentsThreadId,
                    principalTable: "CommentsThreads",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
            });

        migrationBuilder.CreateTable(
            name: "UserProfile",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false),
                Bio = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_UserProfile", x => x.Id);
                table.ForeignKey(
                    name: "FK_UserProfile_CommentsThreads_Id",
                    column: x => x.Id,
                    principalTable: "CommentsThreads",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateIndex(
            name: "IX_ThreadComments_CommentsThreadId",
            table: "ThreadComments",
            column: "CommentsThreadId");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "BlogPost");

        migrationBuilder.DropTable(
            name: "Chapter");

        migrationBuilder.DropTable(
            name: "ThreadComments");

        migrationBuilder.DropTable(
            name: "UserProfile");

        migrationBuilder.DropTable(
            name: "CommentsThreads");
    }
}

Вот скриншот для результата:

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...