使用REST API的ASP.NET Core 2 Web应用程序。当前使用sqlite3开发数据库。 (还尝试了迁移到SQL Server,并得到了与以下相同的结果)。
我正在将一个实体发送到Web客户端,客户端对该实体进行了更改,其中涉及添加一个新的相关实体,然后该更新的主要实体作为PUT主体中的json发送回请求。
我希望新的相关实体会自动创建,但是这没有发生。原则实体上的简单属性会正确更新,但引用属性不会更新。我没有任何异常或任何东西-它似乎正在忽略引用属性。
简化的类(我删除了不应影响关系的其他属性):
public partial class DashboardItem {
public int Id { get; set; }
public int? DataObjectId { get; set; }
public DataObject DataObject { get; set; }
}
public partial class DataObject {
public int Id { get; set; }
}
DbContext Fluent API中与相关属性有关的部分:
modelBuilder.Entity<DashboardItem>(entity => {
entity.HasOne(p => p.DataObject)
.WithMany()
.HasForeignKey(p => p.DataObjectId);
});
PUT的控制器方法:
[HttpPut("{id}")]
public async Task<IActionResult> PutDashboardItem([FromRoute] int id, [FromBody] DashboardItem entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != entity.Id)
{
return BadRequest();
}
_context.Entry(entity).State = EntityState.Modified;
try{
await _context.SaveChangesAsync();
}catch (DbUpdateConcurrencyException)
{
if (!DashboardItemExists(id)){
return NotFound();
}else {
throw;
}
}
return NoContent();
}
简化后的json(不包含所有其他属性)看起来像这样(我尝试了从json中删除外键“ DataObjectId”的不同变体,将其设置为null,或者在可能的情况下设置为零)。干扰。):
{
Id:1,
DataObjectId:null,
DataObject:{
Id: 0
}
}
在控制器操作方法中进行调试时,从请求主体创建的现有“ DashboardItem”主体实体在添加到DbContext之前具有引用属性“ DataObject”,但新的DataObject从未在数据库中创建。对于DashboardItem仅发出SQL UPDATE语句,而对于DataObject不发出INSERT。
我还尝试使用DbContext.SaveChanges()而不是.SaveChangesAsync()使控制器方法同步而不是异步,因为EF Core的早期版本中有used to be a problem与创建相关实体有关,即使我使用的是2.0版本,该版本已经对此进行了修复。结果相同。
This EFCore Doc sounds like it should just work out of the box.
这在上一个项目中对我有用。我在这里想念什么?
答案 0 :(得分:1)
基本上,我的错误是假设从Web应用程序中的客户端发送更新的数据时,更新数据的过程比实际要简单得多。
深入研究之后,似乎是我的控制器方法中处理PUT请求的以下行是问题所在:
_context.Entry(entity).State = EntityState.Modified;
以这种方式将实体条目状态设置为“已修改”会导致Entity Framework Core忽略相关对象的引用属性-生成的SQL UPDATE将仅处理实体表中的列。
This简单的总结最终使我开始正确的道路。
总结我现在学到的东西:
此控制器方法正在处理已编辑并从客户端发送回的“分离”实体。 DbContext尚未跟踪该实体,因为我在每个http请求中都获得了该上下文的新实例(因此该实体被视为“分离的”)。由于尚未对其进行跟踪,因此当将其添加到DbContext时,需要告知上下文该实体是否已更改以及如何处理它。
有几种方法可以告诉DbContext如何处理分离的实体。其中:
(1)将实体状态设置为EntityState.Modified将导致所有属性都包含在SQL更新中(无论它们是否实际上已更改),但不包括相关实体的参考属性:
_context.Entry(entity).State = EntityState.Modified;
(2)添加具有对DbContext.Update的调用的实体,将执行与上述相同的操作,但将包括引用属性,还包括更新中这些实体的所有属性,无论它们是否已更改:< / p>
_context.Update(entity)
方法2为我工作,在这里我只是想让新的相关子实体在更新中创建为其父实体。
除此之外,DbContext.Attach()和DbContext.TrackGraph听起来像您提供了对指定更新中要包括哪些特定属性或相关实体的更多粒度控制。