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

时间:2021-03-23 20:49:56

标签: c# entity-framework relational-database

这个问题已经让我发疯了好几天,坦率地说,现在我觉得自己一无所知,因为我已经搜索了很长时间并尝试了很多东西,但没有一个奏效。所以我想是时候问一个问题了。

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

所以这让我创建了以下设置:

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 不包含 WithRequired 的定义。”

有人可以帮我解决这个问题吗?我快疯了。

4 个答案:

答案 0 :(得分:0)

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

在目录中,在命令行中使用以下命令创建一个新的 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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

下一步:对于您的对象定义,我建议始终将导航属性声明为 virtual。这可确保延迟加载可用作后备,以避免出现空引用异常之类的情况。集合应该自动初始化,以便在创建新的父实体时也可以使用:

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 会创建一个 shadow 属性来表示幕后的 FK 映射。

使用模型构建器或实体类型配置时,只从一侧绘制关系,而不是从两侧绘制。例如,如果您创建了从 Move 到 MoveRating 的 HasMany,则不要添加另一个从 MoveRating 到 Move 的映射。 (它不是必需的,它可能会导致错误)

最后,在读取您的 Move 及其关联的 MoveRatings 集合时,如果您知道自己将需要评级,请在读取 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。 (需要注意并尽可能避免,但比触发空引用异常要好)

答案 3 :(得分:0)

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

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

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