【问题标题】:Entity Framework Core: Update relation with Id only without extra callEntity Framework Core:仅更新与 Id 的关系,无需额外调用
【发布时间】:2017-11-04 02:15:39
【问题描述】:

我正在试图弄清楚如何处理this doc: 中描述的“单一导航属性案例”

假设我们有 2 个模型。

class School
{
   public ICollection<Child> Childrens {get; set;}
   ...
}

class Child
{
    public int Id {get; set;}
    ...
}

所以这是按惯例创建的多对一关系,Child 中没有明确的外键。

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

【问题讨论】:

  • 除非您的 Child 具有父级的导航属性/父级 ID,否则您不能这样做(即:没有原始查询)。 ORM 是关于对象关系的。但是如果不先加载父级,您甚至不知道 7352 是否是有效的父级 ID,因此无论如何您都必须在某一时刻执行此操作,否则在执行操作时会从数据库提供程序处获得难以解析的异常SaveChanges()
  • 不清楚您要实现什么 - 更改现有孩子的父母?
  • @Tseng 对不起,我有点困惑。 7352 这只是一个孩子的身份,而不是父母。并且显示学校可以有很多孩子存在多对一的关系。我假设即使事实上我在Child EF 中没有直接的ParentId 仍然为多对一关系隐式创建它。
  • 然后是父ID,不管它是什么。仅当Child 具有父级的导航属性或父级的外键时,才可能在单个查询中执行此操作,即ctx.Childs.Add(new Child { ParentId = 5 })ParentId 是m:1 的配置主体(或外键) /1:m 关系,那么你可以
  • @Ph0en1x 但是现有的孩子应该已经有家长学校了吧?你不能仅仅通过Id 来创建一个新孩子。 stub 技术在 EF6 中有效,用于添加指向显式多对多关系的链接,但情况并非如此(并且 EF Core 目前不支持带有隐式链接表的多对多)。

标签: c# orm asp.net-core relationship entity-framework-core


【解决方案1】:

基于更新后的问题

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

  • 双向导航属性
  • 至少有一个 ForeignKey/Principal 属性(SchoolId on Child)
  • 拥有父级的影子外键
  • 执行原始查询(这优于使用 ORM 进行强类型化的想法)并同时与数据库无关

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

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

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

DDD 和 ORM 没有很好的协同作用,对此有更好的方法,但需要不同的架构方法(即:CQRS+ES(Command Query Responsibility Segregation with Event Sourcing)。

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

旧答案

如果您的 Child 对象是对父级的导航属性/“反向引用”,则上述情况仅在单个 DB 操作中是可能的。

class School
{
   public ICollection<Child> Childrens {get; set;}
   ...
}

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; }
    ...
}

然后你可以这样做:

ctx.Childs.Add(new Child { Id = 7352, SchoolId = 5,  ... });

当然你首先要知道学校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();

【讨论】:

  • ctx.Childs.Add 将忽略 Id 并尝试添加新的 Child 记录。
  • 是的,这是为了添加一个新的孩子。为了改变学校,没有办法至少检索一次Child 对象(原始查询除外,但这超出了首先使用 ORM 的目的)。但他可以在任何情况下使用父级导航属性或外键保存School 查询(此处为SchoolId
  • docs.microsoft.com/en-us/ef/core/modeling/relationships 在此文档中描述了一个案例 - 称为“单一导航属性”,因此在这种情况下,Post 内部没有明确的 BlogId,但是我非常有信心它是在表中创建的。所以我的问题是关于这个案子的。我认为存在一种方法,当将帖子添加到 Blog.Posts 集合时,它只会改变这个“隐式”BlogId
  • @silent_coder,再次查看示例,Post 中也有属性 BlogId。
  • @E-Bat:实际的建议是不要将实体用于DbContext 之外的any。不适用于域模型,不适用于 webapi,不能用作 Dto,并且对于每种类型总是有单独的类。其他一切最终都会变得混乱或 Db 特定的东西(Id 字段)泄漏到应用程序的其他层
【解决方案2】:

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

是的,这是可能的。您可以创建一个伪造的 stub School 实体实例,仅使用 IdAttach 它到 DbContext(这样告诉 EF 它是 existing )、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();

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

影子属性是实体类中不存在的属性。这些属性的值和状态纯粹在 Change Tracker 中维护。

只要你知道这个名字。在您的情况下,按照惯例,如果没有配置 "SchoolId"

所以不需要假的School实体实例,只要确保Child被附加,然后通过ChangeTracker API简单地设置shadow属性:

context.Attach(child);
context.Entry(child).Property("SchoolId").CurrentValue = schoolId;
context.SaveChanges();

【讨论】:

  • 没想到那样做。不过个人觉得不是很靠谱。如果schoolId 不存在,它不会创建一所新学校吗?并且在稍后查询时还会创建各种其他奇怪的行为,因为学校的引用将从跟踪的缓存中获取?
  • @Tseng 确实。调用者必须绝对确定这两个实体都存在。并且 DbContext 应该是仅用于此操作的短暂实例。可靠性怎么样,如果其中一个实体不存在,调用者将获得DbUpdateException,并且不会对数据库进行任何更改。实际上上面的操作序列会生成一个UPDATE 命令:)
  • 另外,您的代码中现在有一个随机字符串,如果您的数据库字段名称更改,您将不会收到任何通知。
猜你喜欢
  • 2018-11-24
  • 2018-12-10
  • 1970-01-01
  • 2020-01-18
  • 2020-08-10
  • 2022-08-02
  • 2021-04-03
  • 1970-01-01
  • 2018-03-08
相关资源
最近更新 更多