[英]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 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.