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