Entity Framework Code First 外键问题

6

我觉得ForeignKey属性对我来说好像不起作用,但我猜测我的使用方法可能有误 ;)

通过代码更容易理解:

public class BaseCard
{
    public int Id {get ; set; }
    public int BaseCardId { get; set; }

    public List<Skill> Skills { get; set; }
}

public class Skill
{
    public int Id { get; set; }

    public int BaseCardId { get; set; }
    [ForeignKey("BaseCardId")]
    public BaseCard BaseCard { get; set; }
}

当我尝试使用seed方法填充这些对象时,我遇到了以下错误: “INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Skills_dbo.BaseCards_BaseCardId". The conflict occurred in database "Database", table "dbo.BaseCards", column 'Id'.”
在我的看来,在中的尝试指向的列而不是列,但我无法找出原因。
如果我尝试删除的“normal”属性,并将设置为PK(带有属性[Key]),则会出现以下错误: “Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.”
有人知道如何使此代码工作,以便类中的属性将指向的属性,而不是显然的属性吗?
提前感谢!

你的BaseCard对象中为什么有Id和BaseCardId字段? - Sefa
@vgSefa 我将BaseCardId设置为某个数字。而我认为Id是自动生成的。但它需要是一个特定的数字。是否可以将Id设置为特定的数字? - Rob
@vgSefa 我尝试将Id设置为我需要的数字,而不是BaseCardId,但不幸的是这并不起作用。 - Rob
1
将[Key]属性设置为明确指示主键。(这是在现已删除的答案中建议的)。 - Gert Arnold
@Rob 如Gert所建议的那样,设置[Key]属性。并且请记住,当您进行更新时,您需要同时更新数据库。关于EF有许多基础教程,您可能想快速浏览一下它们。 - Sefa
1个回答

9

你可以自己设置BaseCard的Id,但首先你需要使用Fluent Api来指定这个一对多关系。通过这种方式,你不必在模型类中指定属性。你的模型类应该像这样:

public class BaseCard
{
  public int Id {get ; set; }

  public virtual ICollection<Skill> Skills { get; set; }
}

public class Skill
{
  public int Id { get; set; }

  public int BaseCardId { get; set; }
  public virtual BaseCard BaseCard { get; set; }
}

如您所见,我将导航属性更改为虚拟属性。如果您将导航属性定义为虚拟属性,EF会在运行时创建一个新的类(动态代理)来继承您的BaseCard类并使用它(Skill也是如此)。这个新创建的动态类包含了当第一次访问时加载导航属性的逻辑。这个功能被称为延迟加载。它使Entity Framework避免从数据库中加载不需要的整个依赖对象树。您可以在这些帖子中找到有关此主题的更多信息:为什么导航属性在EF中默认为虚拟属性Entity Framework 4.1虚拟属性
我建议您在模型中将Skills属性中的List<>类型更改为ICollection<>类型。正如您在这篇文章中所看到的那样,接口在这种情况下是一个好的实践。它让您在以后指定您想要的实现(可以是List<>)。
现在回到您的问题,在您的Context类中,您需要重写OnModelCreating方法来指定关系和在BaseCards表中不自动生成Id列。
public class YourContext : DbContext
{
    public DbSet<BaseCard> BaseCards { get; set; }

    public DbSet<Skill> Skill { get; set; }

    //...

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure the primary key for BaseCard
        modelBuilder.Entity<BaseCard>().HasKey(t => t.Id);
        //specify no autogenerate the Id Column
        modelBuilder.Entity<BaseCard>().Property(b => b.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        //one-to-many relationship 
        modelBuilder.Entity<Skill>().HasRequired(c => c.BaseCard)
                .WithMany(s => s.Skills)
                .HasForeignKey(c => c.BaseCardId);
    }
}

使用此配置时,您需要始终使用不同的值设置BaseCard对象的Id。

如果您喜欢使用数据注释,可以按以下方式指定相同的内容:

public class BaseCard
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }
    public virtual ICollection<Skill> Skills { get; set; }
}

public class Skill
{
    [Key]
    public int Id { get; set; }

    public int BaseCardId { get; set; }

   [ForeignKey("BaseCardId")]
    public virtual BaseCard BaseCard { get; set; }
}

我的建议是使用Fluent API,它更加灵活,而且你不需要修改你的模型类。你可以在这个帖子中看到一些有用的考虑。


非常感谢您详细的解释,并给了我不止一个选项。我会进行更多的研究,看看我将使用哪个选项。再次感谢。 - Rob
当我使用Fluent API时,在调用update-database -verbose时出现错误。这会从迁移中调用Seed方法。在这里,我尝试填充数据库,但是当将BaseCard保存到上下文中时,我会收到以下错误:当IDENTITY_INSERT设置为OFF时,在表'BaseCards'中无法插入标识列的显式值。这是因为在迁移到DB时OnModelCreating方法没有被调用吗?您还建议我如何填充数据库? - Rob
你删除了旧的数据库并创建了一个新的吗?我认为这是因为你有一个旧的数据库,其中BaseCards的列Id作为Identity。 - ocuenca
我建议您使用DropCreateDatabaseIfModelChanges Initializer。请查看此页面以了解如何使用:http://www.entityframeworktutorial.net/code-first/database-initialization-strategy-in-code-first.aspx。这样,每当模型更改时,数据库都将被删除。 - ocuenca
我已经为此奋斗了两天,而最终解决的方法是什么呢?“正如你所看到的,我将导航属性更改为虚拟的。” - 这最终让我克服了难关。我的外键属性遗漏了“virtual”修饰符。 - brsfan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接