简体   繁体   English

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

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

This issue has been driving me nuts for days, and frankly at the moment I feel like I'm clueless because I've been searching for so long and tried so many things and none of them work.这个问题已经让我发疯了好几天,坦率地说,现在我觉得我一无所知,因为我一直在寻找这么久,尝试了这么多东西,但都没有奏效。 So I thought it would be time to ask a question.所以我想是时候问一个问题了。

I'm trying to create a small console app that works with workout moves (Move) as primary entity.我正在尝试创建一个小型控制台应用程序,将锻炼动作(Move)作为主要实体。 However, as part of the app I also want users to be able leave a rating and intensity for each workout in an entity called MoveRating which collects mentioned ratings whenever a Move is fetched.但是,作为应用程序的一部分,我还希望用户能够在名为 MoveRating 的实体中为每次锻炼留下评分和强度,该实体在获取 Move 时收集提及的评分。

So this lead me to create the following set-up:所以这导致我创建以下设置:

Move.cs移动.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 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; }
    }

Now I know I'm supposed to do something in my DbContext to make this happen, I've been trying stuff like the following:现在我知道我应该在我的 DbContext 中做一些事情来实现这一点,我一直在尝试以下内容:

DbContext (only the OnModelCreating part) 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);
          }

I know it shouldn't be like this, but whatever example I do try to follow it just doesn't work.我知道它不应该是这样的,但是无论我尝试遵循什么例子都是行不通的。 I've tried stuff like:我尝试过类似的东西:

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

Or或者

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

I feel like one of these should work.我觉得其中之一应该可以工作。 But whatever I try to do I can't seem to get it to work.但无论我尝试做什么,我似乎都无法让它发挥作用。 It will give me messages like "MoveRating does not contain a definition for MoveId" or "CollectionNavigationBuilder<Move, MoveRating> does nto contain a definition for WithRequired."它会给我类似“MoveRating 不包含 MoveId 的定义”或“CollectionNavigationBuilder<Move, MoveRating> 不包含 WithRequired 的定义”之类的消息。

Can someone please help me get over this issue?有人可以帮我解决这个问题吗? I'm losing my mind.我正在失去理智。

Create a new directory, call it whatever you want.创建一个新目录,随意命名。

Inside the directory, create a new mvc application with the following command on the command line:在目录中,在命令行中使用以下命令创建一个新的mvc应用程序:

dotnet new mvc --auth Individual  

--auth Individual part ensures you have entity framework libraries referenced. --auth Individual部分确保您引用了entity framework库。

Open your application code in your text editor.在文本编辑器中打开您的应用程序代码。

To create a one to many relationship in EF Core with one Move to many MoveRatings, your entities would look like this (notice the [Key] annotation):要在 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; }
}

In your DbContext, you will have two properties like so:在您的 DbContext 中,您将有两个属性,如下所示:

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

Make sure your ConnectionString is pointing to the correct server and database.确保您的ConnectionString指向正确的服务器和数据库。 You will find this string, in most cases, in appsettings.js .在大多数情况下,您会在appsettings.js中找到此字符串。

Once everything looks good, you will execute the following in the command line (make sure you're inside your project directory):一旦一切看起来都不错,您将在命令行中执行以下命令(确保您位于项目目录中):

dotnet ef migrations add InitialCreate

When that runs successfully, you can see a Migrations folder and there you will see a file with a name that starts with some numbers followed by InitialCreate.cs.成功运行后,您可以看到一个 Migrations 文件夹,在那里您将看到一个文件名,该文件的名称以一些数字开头,后跟 InitialCreate.cs。 You can open it and see that it looks something like this:你可以打开它,看到它看起来像这样:

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");
}

After you're happy, execute the following in the command line:满意后,在命令行中执行以下命令:

dotnet ef database update

That should create the relationship in your database.那应该在您的数据库中创建关系。

Notes笔记

If you don't have EF tools installed, you can install them by executing:如果您没有安装 EF 工具,您可以通过执行以下命令来安装它们:

dotnet tool install --global dotnet-ef

in your command line.在你的命令行中。

References参考

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

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

I'd recommend you to define all relations with the help of ModelBuilder as less as possible, because you need to check out your configuration file each time, when you to rewind your database structure.我建议您尽可能少地在ModelBuilder的帮助下定义所有关系,因为每次回退数据库结构时都需要检查配置文件。 Still it's up to you.仍然取决于你。

Basically, what you need is just add an int MoveId property in MoveRating class, remove your config and that's all.基本上,您只需在MoveRating class 中添加一个int MoveId属性,删除您的配置即可。 It will fetch Move table because of names convention.由于名称约定,它将获取Move表。

If your database already exists and you are not using Code-first + migrations to update it, ensure that your MoveRating table has a MoveId column.如果您的数据库已经存在并且您没有使用 Code-first + 迁移来更新它,请确保您的 MoveRating 表具有 MoveId 列。 It will need one.它需要一个。

Next: for your object definitions I recommend always declaring the navigation properties as virtual .下一步:对于您的 object 定义,我建议始终将导航属性声明为virtual This ensures that lazy loading will be available as a fall-back to avoid things like null reference exceptions.这可确保延迟加载可用作后备,以避免 null 引用异常之类的事情。 Collections should be auto-initialized so they are ready to go when creating new parent entities as well: 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; }
}

Next will be mapping the association between Move and MoveRating.接下来将映射 Move 和 MoveRating 之间的关联。 You could add a MoveId to your MoveRating entity to serve as the FK to Move:您可以将 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; }
}

This is simple and should get you going, but I generally advise against doing this as it creates two sources of truth for the Move reference.这很简单,应该可以帮助您进行操作,但我通常建议您不要这样做,因为它会为 Move 参考创建两个事实来源。 (moveRating.MoveId and moveRating.Move.Id) Updating the FK does not automatically update the reference to "Move" and vice-versa. (moveRating.MoveId 和 moveRating.Move.Id) 更新 FK 不会自动更新对“Move”的引用,反之亦然。 YOu can leverage shadow properties to specify the FK without declaring the FK field in the entity.您可以利用影子属性来指定 FK,而无需在实体中声明 FK 字段。 Given the error you got with HasRequired it looks like you are using EF Core:鉴于您使用HasRequired的错误,您似乎正在使用 EF Core:

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

This is where your MoveRating entity does not have a field for "MoveId" declared.这是您的 MoveRating 实体没有声明“MoveId”字段的地方。 EF will create a shadow property to represent the FK mapping behind the scenes. EF 将创建一个阴影属性来表示幕后的 FK 映射。

When using modelBuilder or EntityTypeConfigurations, only map out relationships from one side, not both sides.使用 modelBuilder 或 EntityTypeConfigurations 时,仅 map 从一侧而不是两侧输出关系。 For instance if you create a HasMany from Move to MoveRating, do not add another mapping from MoveRating back to Move.例如,如果您创建一个从 Move 到 MoveRating 的HasMany ,则不要添加另一个从 MoveRating 回到 Move 的映射。 (It isn't required and it can lead to bugs) (这不是必需的,它可能会导致错误)

Lastly, when reading your Move and it's associated MoveRatings collections, if you know you are going to need the ratings, eager load them when you read the Move entity.最后,在阅读您的 Move 及其关联的 MoveRatings collections 时,如果您知道您将需要评级,请在阅读 Move 实体时立即加载它们。 This generally means avoiding EF methods like Find in favour of Linq methods like Single .这通常意味着避免使用像Find这样的 EF 方法,而使用像Single这样的 Linq 方法。

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

This would fetch the move /w it's collection of Ratings in one SQL call.这将在一个 SQL 调用中获取移动/w 它的评级集合。 By declaring the MoveRatings as virtual provides a safety net that if you forget to use Include on a Move, provided that its DbContext reference is still alive, the MoveRatings can be pulled with a lazy load call when accessed.通过将 MoveRatings 声明为virtual提供了一个安全网,如果您忘记在 Move 上使用 Include,只要它的 DbContext 引用仍然存在,就可以在访问时通过延迟加载调用来拉取 MoveRatings。 (Something to watch out for and avoid whenever possible, but better than triggering a null reference exception) (尽可能注意和避免的事情,但比触发 null 引用异常更好)

Took me a number of days, but the answer to the problem was stupidly simple...花了我几天的时间,但问题的答案非常简单......

It didn't take the MoveRatings because I tried to access them outside of the data access layer where I defined my DbContext.. That was all..它没有使用 MoveRatings,因为我试图在定义 DbContext 的数据访问层之外访问它们。仅此而已。

Don't know if I should keep this open for visisbility (since I couldn't find this answer anywhere) or if I should delete不知道我是否应该保持这个开放的可见性(因为我在任何地方都找不到这个答案)或者我是否应该删除

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

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