实体框架核心:仅在没有额外调用的情况下更新与Id的关系

sil*_*der 12 c# orm relationship entity-framework-core asp.net-core

我正在试图弄清楚如何处理本文档中描述的"单一导航属性案例" :

假设我们有2个型号.

class School
{
   public ICollection<Child> Childrens {get; set;}
   ...
}
Run Code Online (Sandbox Code Playgroud)

class Child
{
    public int Id {get; set;}
    ...
}
Run Code Online (Sandbox Code Playgroud)

所以它是由约定创建的多对一关系,没有明确的外键Child.

所以问题是我们是否有Child实例并知道School.Id有没有办法更新这种关系,而无需额外调用数据库来获取School实例.

Iva*_*oev 13

所以问题是我们是否有Child实例并知道School.Id有没有办法更新这种关系,而无需额外调用数据库来获取School实例.

是的,这是可能的.您可以创建一个假的存根 School实体实例与Id而已,Attach它的DbContext(这种方式告诉EF,它是现有的),AttachChild实例出于同样的原因,然后添加Child到父集合,并呼吁SaveChanges:

Child child = ...;
var schoolId = ...;

var school = new School { Id = schoolId };
context.Attach(school);
context.Attach(child);
school.Childrens.Add(child);
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

更新:实际上还有另一种清洁方式,因为即使实体没有导航或FK属性,EF Core也允许您访问/修改所谓的阴影属性

阴影属性是实体类中不存在的属性.这些属性的值和状态仅在Change Tracker中维护.

一旦你知道这个名字.在您的情况下,没有配置将按惯例"SchoolId".

因此,不需要伪School实体实例,只需确保Child附加,然后通过ChangeTracker API简单地设置shadow属性:

context.Attach(child);
context.Entry(child).Property("SchoolId").CurrentValue = schoolId;
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)


Tse*_*eng 3

根据更新的问题

不,没有任何方法可以通过使用 ORM 和 ORM 提供的强类型来做到这一点,无需

  • 双向导航属性
  • 至少有一个ForeignKey/Principal属性(SchoolIdChild
  • 拥有父级的影子外键
  • 执行原始查询(这击败了使用 ORM 实现强类型的想法),同时与数据库无关

    // Bad!! Database specific dialect, no strong typing 
    ctx.Database.ExecuteSqlCommandAsync("UPDATE Childs SET schoolId = {0}", schoolId);
    
    Run Code Online (Sandbox Code Playgroud)

当您选择使用 ORM 时,您必须接受相关 ORM 框架的某些技术限制。

如果您想遵循领域驱动设计(DDD)并从实体中删除所有数据库特定字段,那么将域模型用作实体并不容易。

DDD 和 ORM 没有很好的协同作用,对此有更好的方法,但需要不同的架构方法(即:CQRS+ES(带有事件源的命令查询职责分离)。

这对于 DDD 效果更好,因为来自 EventSourcing 的事件只是简单(且不可变)的消息类,可以将其作为序列化 JSON 存储在数据库中并重播以重建域实体的状态。但这是另一回事了,人们可以就这个主题写一整本书。

旧答案

Child如果您的对象是父级的导航属性/“反向引用” ,则上述场景仅在单个数据库操作中可能出现。

class School
{
   public ICollection<Child> Childrens {get; set;}
   ...
}
Run Code Online (Sandbox Code Playgroud)

class Child
{
    public int Id {get; set;}
    // this is required if you want do it in a single operation
    public int SchoolId { get; set; }
    // this one is optional
    public School { get; set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后你可以做类似的事情:

ctx.Childs.Add(new Child { Id = 7352, SchoolId = 5,  ... });
Run Code Online (Sandbox Code Playgroud)

当然,你首先必须知道学校 ID 并知道它是有效的,否则如果SchoolId是无效值,操作将抛出异常,所以我不推荐这种方法。

如果您只有一个childId而不添加一个全新的孩子,您仍然必须先获得孩子。

// childId = 7352
var child = ctx.Childs.FirstOrDefault(c => c.Id == childId);
// or use ctx.Childs.Find(childId); if there is a chance that 
// some other operation already loaded this child and it's tracked

// schoolId = 5 for example
child.SchoolId = schoolId;
ctx.SaveChanges();
Run Code Online (Sandbox Code Playgroud)