这个问题已经让我发疯了好几天,坦率地说,现在我觉得自己一无所知,因为我已经搜索了很长时间并尝试了很多东西,但没有一个奏效。所以我想是时候问一个问题了。
我正在尝试创建一个以锻炼动作 (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
有人可以帮我解决这个问题吗?我快疯了。
答案 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 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 的数据访问层之外访问它们.. 仅此而已..
不知道我是否应该将其保持打开状态以保持可见性(因为我在任何地方都找不到这个答案)或者我是否应该删除