为什么我需要在 Entity Framework Core 关系中传递模型类而不是 id

Why do I need to pass the model class instead of id in Entity Framework Core relationship

提问人:guirms 提问时间:10/10/2023 最后编辑:marc_sguirms 更新时间:10/11/2023 访问量:77

问:

假设我在 .NET 中有一个名为“B”的模型类,它具有模型类“A”所需的 fk(一对一关系)。

模型的代码应如下所示:

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

    public virtual required B ClassB { get; set; }
}

public class B
{
   public int Id { get; set; }
   public int A_ID { get; set; }

    public virtual required A ClassB { get; set; }
}

现在,假设我在表 “A” 中有一条 id 为“1” 的记录,我想在类 “B” 中添加一条记录,该记录的 fk 为 1。

在我看来,这段代码应该足够了:

var classBToAdd = new B
{
   A_ID = 1
}

_myRepo.Add(classBToAdd);

但我不能这样做,因为关系是必需的:

public virtual required A ClassB { get; set; }

如果不是必需的,我会收到以下警告:

退出构造函数时,不可为 null 的变量必须包含非 null 值。请考虑将其声明为可为 null。

为什么需要指定完整的对象?在某些情况下,这可能意味着我需要执行一个大查询才能保存完整的对象。

C# .NET 数据库 实体-框架-核心 关系

评论

1赞 Neil 10/10/2023
您正在考虑数据库术语而不是 ORM 术语。删除 Id 属性(EF 将自动添加它们),并使用对象属性而不是 Id 属性。
1赞 jdweng 10/10/2023
实体创建一个层次结构模型,该模型将 c# 类/属性映射到数据库表/列。实体接口使用模型,并且必须发送模型而不是属性。

答:

0赞 Fabio Caruso 10/10/2023 #1

代码中的 public int A_ID { get; set; } 行不会自动标识外键。正确的代码是:

public int AId { get; set; }

如果 A 类对象需要尚不存在的 B 类对象,如何先创建该类对象(以获取其引用 ID)?

此代码有效:

public class A
{
    public int Id { get; set; }
    public virtual B ClassB { get; set; }
}

public class B
{
    public int Id { get; set; }
    public int AId { get; set; }
    public virtual A ClassA { get; set; }
}

...并在您的 Main() 中:

A newClassA = new A();                                    
db.Add(newClassA);
db.SaveChanges();
            
B classBToAdd = new B
{
    AId = newClassA.Id
};

db.Add(classBToAdd);
db.SaveChanges();

评论

0赞 Steve Py 10/10/2023
不要这样做...... 应仅调用一次,因为 DbContext 表示工作单元。如果同时创建 A 和相关 B,则创建 A,创建通过导航属性将其关联到 A 的 B,然后调用 。EF 自动管理插入顺序和 FK 关联。SaveChangesSaveChanges()
1赞 Steve Py 10/10/2023 #2

法比奥走在正确的轨道上,但有一个更简单的答案。用于标识 FK 的 EF 约定基于类型,而不是属性名称。这意味着,如果要声明不遵循隐含约定的 FK,则需要告诉 EF 要使用什么:

public class B
{
    public int Id { get; set; }
 
    public int A_ID { get; set; }
    [ForeignKey(nameof(A_ID))]
    public virtual A ClassA { get; set; }
}

您不需要必需的,因为 A 和 A_ID 都不能为 null。现在,如果要单独创建 B,只需将其与 A 记录关联,而不使用 ClassA 导航属性,则它应该按预期工作。

如果您要同时创建 A 和 B,则采用最简单的形式:

var newA = new A() { /* populate A details */ }; 
newA.Bs.Add(new B() { /* populate B details */ });
_context.As.Add(newA);
_context.SaveChanges();

如果关系映射正确,EF 会自动计算出插入顺序和 FK 关联,这就是使用 ORM 与 ADO 样式 CRUD 操作的意义所在。

我建议不要在 EF DbContext 之上使用似乎是存储库的模式。DbContext 已充当工作单元容器,而 DbSet 充当存储库,因此尝试抽象这些既没有必要,也容易给 EF 可以提供的功能和性能带来问题和限制。可以说,在 EF 上引入 UoW 和 Repository 模式是有正当理由的,例如促进单元测试或实现核心过滤/授权检查,但在开始时,我建议直接使用 DbContext 和 DbSets,以避免引入复杂性。

编辑:不可为 Null 的引用类型错误和 EF。编译器将发出警告,或者如果您的项目选择了严格强制执行,并且您有未填充的必需 ClassA,则会出现异常。

解决此问题的最正确方法是:

[Required]
public virtual A? ClassA { get; set; }

其他选项包括:

public virtual A ClassA { get; set; } = null!;

或者对构造函数使用宽恕标记:

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
    public B()
    { }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
  • 请注意,此构造函数用于类 B,并禁用此类的警告/异常,因此在我们的示例中,关于非 null ClassA 引用。我通常有公共构造函数来初始化具有所有必填字段的实体,因此您可以将此默认构造函数标记为 ,否则,如果您只是在实体上使用默认构造函数(或没有构造函数),请使此 .protectedpublic

评论

1赞 Fabio Caruso 10/11/2023
我认为选择键、外键、关系、所需属性、行版本等的最佳方式。正在重写 OnModelCreating。此外,可能还有一种更简单的方法来创建这两个实体:B classBToAdd = new B { ClassA = new A() }; db。Add(类BToAdd);分贝。保存更改();
0赞 guirms 10/11/2023
但是如果这样做“公共虚拟 A ClassA { get;设置;}“,我仍然收到错误”退出构造函数时,不可为空的变量必须包含非空值。请考虑将其声明为可为 null。
0赞 Steve Py 10/11/2023
是的,EF 的惯例是一把双刃剑。它们可以节省一些时间,但当你需要一些不太适合的东西时,可能会浪费时间,除非你认识到你偏离了他们的惯例,否则他们会猜测为什么它不起作用。属性可以完成 95% 的繁重工作,但仍有一些围绕关系的方案,例如一些非常需要显式配置的多对多方案。我喜欢使用 IEntityTypeConfiguration 作为 OnModelCreating 的一部分来很好地划分配置。
1赞 Steve Py 10/11/2023
啊,可以为 null 的引用。您收到警告还是异常?如果出现异常,则您的项目已选择将 CS8618 升级为异常。这对于 EF 实体来说可能很麻烦,其中 EF 管理所需的引用,但 C# 可为 null 的引用会抱怨。您可以使用属性使其可为 null,或者使用或标记的构造函数告诉可为 null 的引用检查是安静的。我将用例子更新答案。[Required]= null!;