EF Core - 添加相关实体时出错
Posted
技术标签:
【中文标题】EF Core - 添加相关实体时出错【英文标题】:EF Core - Error when adding a related entity 【发布时间】:2018-02-12 22:02:06 【问题描述】:当我尝试更新已从数据库中获取的实体的相关实体时出现错误。出于说明目的,我有这些实体:
class Car
int Id ..;
string Name ..;
virtual ICollection<TireCar> tires ...;
class TireCar
int Id ..;
int TireId ..;
int CarId..;
int Size..;
virtual TireBrand tire;
virtual Car car;
class TireBrand
int Id;
string Name ..;
所以,我正在尝试制作一个 Patch 方法,该方法允许我更新 Car
数据并添加、更新或删除 tires
。当我获得Car
实体并添加Tire
时,就会出现问题。类似的东西:
void UpdateCar()
var car = carService.Get(...);
...
carService.AddTire(new TireCar CarId = car.Id, TireId = 1 );
...
我在 DI 中使用 Repository 模式,所以上下文是相同的。抛出的错误是:
System.InvalidOperationException:实体类型“Car”和“TireCar”之间的关联已被切断,但此关系的外键不能设置为 null。如果应该删除依赖实体,则设置关系以使用级联删除。'
我尝试了两种可行的方法,但我认为不是解决方案:
添加轮胎后我得到汽车时 当我得到汽车时没有跟踪如果我要更新其他表,为什么会发生这种情况?我该怎么做才能解决这个问题?
【问题讨论】:
【参考方案1】:您的问题实际上是配置问题,而不是 EF Core 的问题。仔细阅读错误内容:
实体类型“Car”和“TireCar”之间的关联已被切断,但此关系的外键不能设置为 null。如果应该删除依赖实体,则设置关系以使用级联删除。
Entity Framework Core 在依赖实体成为孤立实体时默认(.NET Core 2.0 向前)具有 SET NULL 策略。如果我们仔细查看您的TireCar
模型,您没有将CarId
属性设置为可为空,因此该列不能设置为空。
您有两种不同的解决方案来解决此问题。如果您希望在从 Car
实体中删除 TireCar
实体时将其删除,请为此关系设置级联删除(您也可以更改 EF Core 默认策略)。这可以通过FluentApi
在您的DbContext
中设置。
class MyContext : DbContext
// Your DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<TireCar>()
.HasOne(tc => tc.car)
.WithMany(car => car.tires)
.OnDelete(DeleteBehavior.Cascade);
另一个解决方案是让TireCar
实体在CarId
列中具有null
值,只需像这样更改您的实体模型。
class TireCar
public int Id get; set;
public int TireId get; set;
public int? CarId get; set;
public int Size get; set;
public virtual TireBrand tire get; set;
public virtual Car car get; set;
【讨论】:
我认为这不是问题所在。我发现了两个“解决方案”,它们在相同的映射中不会出错。为什么在调用 AddTire 方法后获取 Car 时会创建 TireCar?或者为什么当我得到汽车“AsNoTracking”时会创建 TireCar? 当你说添加轮胎后取车时它可以工作,你的意思是当你再次查询取车时?我猜您正在用其他轮胎替换一些汽车轮胎(这是我想到的唯一原因,为什么 EF Core 应该尝试使用ClientSetNull
进行级联。它会与 AsNoTracking
“工作”,因为即使您卸下汽车轮胎如果查询不跟踪更改,则来自Car.tires
属性的值不会设置为空。在您的问题中添加一些存储库代码,以更清楚地了解错误的来源。【参考方案2】:
来自 EF Core 文档:
按照惯例,级联删除将被设置为级联 关系和 ClientSetNull 用于可选关系。级联 表示依赖实体也被删除。 ClientSetNull 意味着 未加载到内存中的依赖实体将保留 未更改,必须手动删除,或更新为指向有效的 主体。对于加载到内存中的实体,EF Core 将尝试将外键属性设置为 null。
使用连接实体类型配置和 NuGet Microsoft.EntityFrameworkCore.SqlServer
使用 Cascade 的多对多示例:
internal class MyContext : DbContext
public MyContext(DbContextOptions<MyContext> options)
: base(options)
public DbSet<Post> Posts get; set;
public DbSet<Tag> Tags get; set;
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId),
j =>
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new t.PostId, t.TagId );
);
public class Post
public int PostId get; set;
public string Title get; set;
public string Content get; set;
public ICollection<Tag> Tags get; set;
public List<PostTag> PostTags get; set;
public class Tag
public string TagId get; set;
public ICollection<Post> Posts get; set;
public List<PostTag> PostTags get; set;
public class PostTag
public DateTime PublicationDate get; set;
public int PostId get; set;
public Post Post get; set;
public string TagId get; set;
public Tag Tag get; set;
https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=data-annotations%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration
如果您引入更多路径,您最终可能会使用Microsoft.EntityFrameworkCore.Tools
或类似命令在Update-Database
上获得以下异常。
Microsoft.Data.SqlClient.SqlException (0x80131904):介绍 表 '' 上的 FOREIGN KEY 约束 '' 可能会导致循环或多个 级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或 修改其他 FOREIGN KEY 约束。无法创建约束或 指数。查看以前的错误。
一种解决方案可能是简单地将DeleteBehavior.Restrict
添加到关系中,但是在删除父实体时会再次出现类似错误,除非您自己清除每个关系。
例子:
实体类型 '' 和 '' 之间的关联已被切断,但是 该关系要么被标记为必需,要么被隐含 需要,因为外键不可为空。如果 当需要关系时,应删除依赖/子实体 被切断,配置关系使用级联删除。 考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来 查看关键值
您可以通过指定 DeleteBehavior.ClientCascade
来解决此问题,这将允许 EF 对加载的实体执行级联删除。
internal class MyContext : DbContext
public MyContext(DbContextOptions<MyContext> options)
: base(options)
public DbSet<Post> Posts get; set;
public DbSet<Tag> Tags get; set;
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId)
.OnDelete(DeleteBehavior.Cascade),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId)
.OnDelete(DeleteBehavior.ClientCascade),
j =>
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new t.PostId, t.TagId );
);
public class Post
public int PostId get; set;
public string Title get; set;
public string Content get; set;
public ICollection<Tag> Tags get; set;
public List<PostTag> PostTags get; set;
public class Tag
public string TagId get; set;
public ICollection<Post> Posts get; set;
public List<PostTag> PostTags get; set;
public class PostTag
public DateTime PublicationDate get; set;
public int PostId get; set;
public Post Post get; set;
public string TagId get; set;
public Tag Tag get; set;
https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0
【讨论】:
以上是关于EF Core - 添加相关实体时出错的主要内容,如果未能解决你的问题,请参考以下文章
EF sqlite3报错 "System.Data.Entity.Core.EntityException: 在提供程序连接上启动事务时出错。有关详细信息,请参阅内部异常。
执行迁移 EF core 2.0 时出错,将身份 ID 从字符串更改为 int