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 - 无法添加具有相同相关对象的实体

使用 EF Core 保存附加实体时如何删除子实体

执行迁移 EF core 2.0 时出错,将身份 ID 从字符串更改为 int

Swift Core Data - 使用 NSManagedObject 子类访问获取的实体时出错

将 mogenerator 与 Core Data 实体一起使用会导致保存数据存储时出错