繁体   English   中英

C# 实体框架:创建一对多关系

[英]C# Entity Framework: creating one-to-many relationship

这个问题已经让我发疯了好几天,坦率地说,现在我觉得我一无所知,因为我一直在寻找这么久,尝试了这么多东西,但都没有奏效。 所以我想是时候问一个问题了。

我正在尝试创建一个小型控制台应用程序,将锻炼动作(Move)作为主要实体。 但是,作为应用程序的一部分,我还希望用户能够在名为 MoveRating 的实体中为每次锻炼留下评分和强度,该实体在获取 Move 时收集提及的评分。

所以这导致我创建以下设置:

移动.cs

    public class Move
    {
        public int Id { get; set; }
        public string MoveName { get; set; }
        public string MoveDescription { get; set; }
        public int SweatRate { get; set; }
        public ICollection<MoveRating> MoveRating { get; set; }
    }

MoveRating.cs

    public class MoveRating
    {
        public int Id { get; set; }
        public Move Move { get; set; }
        public double Rating { get; set; }
        public double Intensity { get; set; }
    }

现在我知道我应该在我的 DbContext 中做一些事情来实现这一点,我一直在尝试以下内容:

DbContext(仅 OnModelCreating 部分)

      protected override void OnModelCreating(ModelBuilder builder)
          {
            builder.Entity<MoveRating>().HasOne(m => m.Move).WithMany(a => a.MoveRating);
            builder.Entity<Move>().HasMany(m => m.MoveRating).WithOne(g => g.Move);
          }

我知道它不应该是这样的,但是无论我尝试遵循什么例子都是行不通的。 我尝试过类似的东西:

    builder.Entity<MoveRating>().HasOne(b => b.Move).WithMany(m => m.MoveRating).HasForeignKey(m => m.MoveId);

或者

    builder.Entity<Move>().HasMany(m => m.MoveRating).WithRequired(m => m.MoveRating);

我觉得其中之一应该可以工作。 但无论我尝试做什么,我似乎都无法让它发挥作用。 它会给我类似“MoveRating 不包含 MoveId 的定义”或“CollectionNavigationBuilder<Move, MoveRating> 不包含 WithRequired 的定义”之类的消息。

有人可以帮我解决这个问题吗? 我正在失去理智。

创建一个新目录,随意命名。

在目录中,在命令行中使用以下命令创建一个新的mvc应用程序:

dotnet new mvc --auth Individual  

--auth Individual部分确保您引用了entity framework库。

在文本编辑器中打开您的应用程序代码。

要在 EF Core 中使用一个 Move 到多个 MoveRatings 创建一对多关系,您的实体将如下所示(注意[Key]注释):

public class Move
{
    [Key]
    public int Id { get; set; }
    public string MoveName { get; set; }
    public string MoveDescription { get; set; }
    public int SweatRate { get; set; }
    public List<MoveRating> MoveRating { get; set; }
}

public class MoveRating
{
    [Key]
    public int Id { get; set; }
    public Move Move { get; set; }
    public double Rating { get; set; }
    public double Intensity { get; set; }
}

在您的 DbContext 中,您将有两个属性,如下所示:

public DbSet<Move> Moves { get; set; }
public DbSet<MoveRating> MoveRatings { get; set; }

确保您的ConnectionString指向正确的服务器和数据库。 在大多数情况下,您会在appsettings.js中找到此字符串。

一旦一切看起来都不错,您将在命令行中执行以下命令(确保您位于项目目录中):

dotnet ef migrations add InitialCreate

成功运行后,您可以看到一个 Migrations 文件夹,在那里您将看到一个文件名,该文件的名称以一些数字开头,后跟 InitialCreate.cs。 你可以打开它,看到它看起来像这样:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "Moves",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            MoveName = table.Column<string>(type: "TEXT", nullable: true),
            MoveDescription = table.Column<string>(type: "TEXT", nullable: true),
            SweatRate = table.Column<int>(type: "INTEGER", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Moves", x => x.Id);
        });

    migrationBuilder.CreateTable(
        name: "MoveRatings",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            MoveId = table.Column<int>(type: "INTEGER", nullable: true),
            Rating = table.Column<double>(type: "REAL", nullable: false),
            Intensity = table.Column<double>(type: "REAL", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_MoveRatings", x => x.Id);
            table.ForeignKey(
                name: "FK_MoveRatings_Moves_MoveId",
                column: x => x.MoveId,
                principalTable: "Moves",
                principalColumn: "Id",
                onDelete: ReferentialAction.Restrict);
        });

    migrationBuilder.CreateIndex(
        name: "IX_MoveRatings_MoveId",
        table: "MoveRatings",
        column: "MoveId");
}

满意后,在命令行中执行以下命令:

dotnet ef database update

那应该在您的数据库中创建关系。

笔记

如果您没有安装 EF 工具,您可以通过执行以下命令来安装它们:

dotnet tool install --global dotnet-ef

在你的命令行中。

参考

EF 核心 - 关系: https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key

EF Core - 工具参考: https://docs.microsoft.com/en-us/ef/core/cli/dotnet

我建议您尽可能少地在ModelBuilder的帮助下定义所有关系,因为每次回退数据库结构时都需要检查配置文件。 仍然取决于你。

基本上,您只需在MoveRating class 中添加一个int MoveId属性,删除您的配置即可。 由于名称约定,它将获取Move表。

如果您的数据库已经存在并且您没有使用 Code-first + 迁移来更新它,请确保您的 MoveRating 表具有 MoveId 列。 它需要一个。

下一步:对于您的 object 定义,我建议始终将导航属性声明为virtual 这可确保延迟加载可用作后备,以避免 null 引用异常之类的事情。 Collections 应自动初始化,以便在创建新父实体时也准备好 go:

public class Move
{
    [Key]
    public int Id { get; set; }
    public string MoveName { get; set; }
    public string MoveDescription { get; set; }
    public int SweatRate { get; set; }
    public virtual ICollection<MoveRating> MoveRating { get; set; } = new List<MoveRating>();
}

public class MoveRating
{
    [Key]
    public int Id { get; set; }
    public virtual Move Move { get; set; }
    public double Rating { get; set; }
    public double Intensity { get; set; }
}

接下来将映射 Move 和 MoveRating 之间的关联。 您可以将 MoveId 添加到您的 MoveRating 实体中,作为移动的 FK:

public class MoveRating
{
    [Key]
    public int Id { get; set; }
    [ForeignKey("Move")]
    public int MoveId { get; set; }
    public virtual Move Move { get; set; }
    public double Rating { get; set; }
    public double Intensity { get; set; }
}

这很简单,应该可以帮助您进行操作,但我通常建议您不要这样做,因为它会为 Move 参考创建两个事实来源。 (moveRating.MoveId 和 moveRating.Move.Id) 更新 FK 不会自动更新对“Move”的引用,反之亦然。 您可以利用影子属性来指定 FK,而无需在实体中声明 FK 字段。 鉴于您使用HasRequired的错误,您似乎正在使用 EF Core:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Move>()
        .HasMany(m => m.MoveRating)
        .WithOne(g => g.Move)
        .HasForeignKey("MoveId");
}

这是您的 MoveRating 实体没有声明“MoveId”字段的地方。 EF 将创建一个阴影属性来表示幕后的 FK 映射。

使用 modelBuilder 或 EntityTypeConfigurations 时,仅 map 从一侧而不是两侧输出关系。 例如,如果您创建一个从 Move 到 MoveRating 的HasMany ,则不要添加另一个从 MoveRating 回到 Move 的映射。 (这不是必需的,它可能会导致错误)

最后,在阅读您的 Move 及其关联的 MoveRatings collections 时,如果您知道您将需要评级,请在阅读 Move 实体时立即加载它们。 这通常意味着避免使用像Find这样的 EF 方法,而使用像Single这样的 Linq 方法。

var move = context.Moves
    .Include(m => m.MoveRatings)
    .Single(m => m.MoveId == moveId);

这将在一个 SQL 调用中获取移动/w 它的评级集合。 通过将 MoveRatings 声明为virtual提供了一个安全网,如果您忘记在 Move 上使用 Include,只要它的 DbContext 引用仍然存在,就可以在访问时通过延迟加载调用来拉取 MoveRatings。 (尽可能注意和避免的事情,但比触发 null 引用异常更好)

花了我几天的时间,但问题的答案非常简单......

它没有使用 MoveRatings,因为我试图在定义 DbContext 的数据访问层之外访问它们。仅此而已。

不知道我是否应该保持这个开放的可见性(因为我在任何地方都找不到这个答案)或者我是否应该删除

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM