【问题标题】:EF Core - Error when adding a related entityEF Core - 添加相关实体时出错
【发布时间】: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。如果应该删除依赖实体,则设置关系以使用级联删除。'

我尝试了两种可行的方法,但我认为不是解决方案:

  • 添加轮胎后我得到汽车时
  • 当我得到汽车时没有跟踪

如果我要更新其他表,为什么会发生这种情况?我该怎么做才能解决这个问题?

【问题讨论】:

    标签: c# entity-framework .net-core entity-framework-core


    【解决方案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

    【讨论】:

      猜你喜欢
      • 2017-11-07
      • 1970-01-01
      • 2019-12-07
      • 2016-08-01
      • 1970-01-01
      • 2021-05-03
      • 2020-09-04
      • 1970-01-01
      • 2020-04-09
      相关资源
      最近更新 更多