我正在 Blazor 中构建一个习惯跟踪应用程序,并使用 ASP.NET Core 6 Web API 作为后端,该后端使用实体框架。我有一个
Habit
实体,它拥有许多这样的实现实体:
modelBuilder.Entity<Habit>().OwnsMany(h => h.Implementations);
我正在尝试为已有实现的习惯添加一个实现。这是我的控制器操作方法中的代码:
public async Task<IActionResult> AddHabitReview([FromBody] HabitReviewModel model)
{
try
{
var habit = await _context.Habits.FindAsync(model.Id);
if (habit == null)
return NotFound("No habit found");
habit.AddImplementation(/*implementation parameters*/);
_context.Habits.Update(habit);
await _context.SaveChangesAsync();
return Ok();
}
catch (Exception ex)
{
var message = ex.Message;
return BadRequest(message);
}
}
但是我遇到以下异常:
数据库操作预计影响1行,实际影响0行;自加载实体以来,数据可能已被修改或删除。有关理解和处理乐观并发异常的信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=527962。
我还通过 SQL Server Profiler 检查了数据库查询。由于某种原因,它正在为
UPDATE
表创建 Implementation
命令,并将 Id
作为我的新实现 ID。我希望它能为实现表生成一个 CREATE 命令,因为我正在创建一个新的实现并将其附加到习惯实体。
有人可以指出我做错了什么吗?
错误的根源在于 EF Core 尝试更新 Habit 实体,但没有正确处理 Implemention 集合的更改。
所以正确的用法如下:
var habit = await _context.Habits.FindAsync(model.Id);
if (habit == null)
return NotFound("No habit found");
// create new obj
var newImplementation = new Implementation(...);
// Add new Implementation instance in `habit.Implementations`
habit.Implementations.Add(newImplementation);
// Since we have add new Implementation instance into the Habit collection
// So no need to explicitly call the Update method
await _context.SaveChangesAsync();
return Ok();
简短回答
public class Configuration : IEntityTypeConfiguration<Habit>
{
public void Configure(EntityTypeBuilder<Habit> builder)
{
builder.HasKey(w => w.Id); // maybe
builder.OwnsMany(w => w.Implementations, a =>
{
a.WithOwner().HasForeignKey(w => w.HabitId);
a.Property<int>("Id"); // in case you have not in an entity
a.HasKey("Id");
});
}}
顺便说一下你可以做
var habit = await _context.Habits.FindAsync(model.Id);
if (habit == null)
return NotFound("No habit found");
var implementation = new Implementation();
_context.Add(implementation);
habit.Implementations.Add(implementation);
await _context.SaveChangesAsync();
return Ok();
长答案
自有类型需要主键。如果 .NET 类型没有好的候选属性,EF Core 可以尝试创建一个。然而,当通过集合定义拥有的类型时,仅仅创建一个影子属性来充当所有者的外键和拥有的实例的主键是不够的,就像我们对 OwnsOne 所做的那样:每个所有者拥有多个拥有的类型实例,因此所有者的密钥不足以为每个拥有的实例提供唯一的身份。
看这个拥有类型的集合
并且需要添加到集合中才能生成CREATE到数据库中
在这里您可以看到它是如何工作的DetectChanges 尊重商店生成的键值