删除可选相关实体时如何将 FK 更新为 null

Posted

技术标签:

【中文标题】删除可选相关实体时如何将 FK 更新为 null【英文标题】:How to update FK to null when deleting optional related entity 【发布时间】:2016-02-28 00:19:55 【问题描述】:

我对 EF 相当陌生,并且有点努力以方便删除我的对象。我的两个对象和关联的 DbContext 如下所示:

public class Context: DbContext

    public Context() : base()
    public DbSet<Person> Persons get;set;
    public DbSet<Vehicle> Vehicles get;set;


public class Person

   public int PersonID get;set;
   public string Name get;set;


public class Vehicle

   public int VehicleID get;set;

   public int? PersonID get;set;

   [ForeignKey("PersonID")]
   public virtual Person Person get;set;
 

如上所述,一个人可以链接到多辆车。人与车辆之间没有显式链接,但通过外键关系,车辆与“父”人之间存在链接。

然后我在我的代码中创建各种车辆,并将它们链接到 可选 个人对象(外键可以为空)。

我的问题是关于删除 Person 对象。我通常按​​如下方式删除对象:

private void DeletePerson()

    using (var context = new Context())
    
        int personID = 4; //Determined through other code
        var person = context.Persons.Find(personID);
        context.Persons.Remove(person);
        context.SaveChanges();
    

但是上面的代码会失败,给我一个引用约束异常(由于车辆外键)。但是,我希望与特定人员相关联的所有车辆的外键都被简单地设置为空?

我能够通过将所有相关车辆显式加载到上下文中来使代码正常工作,如下所示:

private void DeletePerson()

    using (var context = new Context())
    
        //Determined through other code
        int personID = 4; 
        // Single vehicle associated with this person, there can be multiple vehicles
        int vehicleID = 6; 

        var person = context.Persons.Find(personID);
        // Seems to force loading of the vehicle, facilitating setting 
        // its "PersonID" property to null
        var vehicle = context.Vehicles.Find(vehicleID); 
        context.Persons.Remove(person);
        context.SaveChanges();
    

然而,上面代码的问题是我需要在我的 Person 类中创建一个 List 对象,其中包含对所有潜在依赖对象的引用(或 ID)(车辆只是这里的一个示例,还有其他各种与 Person 具有相似关系的相似类)。

在 Person 对象中创建这个 List 是唯一的方法吗?是否有某种方法可以自动创建此列表/自动添加家属?我宁愿不必通过我的 Person 类中的列表对象来显式管理这些关系。

谢谢!

【问题讨论】:

【参考方案1】:

虽然 SQL Server 支持它,但正如您所猜测的,EF 无法设置级联规则以在删除相关对象时使 FK 无效:Entity Framework: Set Delete Rule with CodeFirst

因此您需要在上下文中包含相关对象,这样当您删除Person 时,相关车辆会更新为空PersonId。您无需为此提供列表。您可以让DbContext 了解相关实体,如下所示:

ctx.Vehicles.Where(v => v.PersonId == personId).Load();

然后,如果你调用 delete,它会按预期工作。

这是一个示例DbContext,配置了fluent API,可以正常工作:

public class SampleDbContext: DbContext

    public SampleDbContext()
        : base("name=CascadeOnDelete")
    

    

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        modelBuilder.Entity<Vehicle>()
            .HasOptional(v => v.Person)
            .WithMany()
            .HasForeignKey(v => v.PersonId);
            //.WillCascadeOnDelete();
        base.OnModelCreating(modelBuilder);
    

    public DbSet<Person> Persons get;set;
    public DbSet<Vehicle> Vehicles get;set;


public class Person

    public int PersonId get;set;
    public string Name get;set;


public class Vehicle

    public int VehicleId get;set;
    public string Model  get; set; 
    public int? PersonId  get; set; 
    public virtual Person Person get;set;
 

这个控制台应用程序显示了预期的行为:

class Program

    static void Main(string[] args)
    
        using (var ctx = new SampleDbContext())
        
            Console.WriteLine("Creating John McFlanagan and their 2 vehicles");
            var person = new Person Name = "John McFlanagan";
            var vehicle1 = new Vehicle  Person = person, Model = "Vauxhall Astra" ;
            var vehicle2 = new Vehicle  Person = person, Model = "Ford Capri" ;

            ctx.Vehicles.AddRange(new[] vehicle1, vehicle2);
            ctx.SaveChanges();
        

        using (var ctx = new SampleDbContext())
        
            var person = ctx.Persons.First();
            // Loading related vehicles in the context
            ctx.Vehicles.Where(v => v.PersonId == person.PersonId).Load();
            Console.WriteLine("Deleting the person, and nullifying vehicles PersonId");
            ctx.Persons.Remove(person);
            ctx.SaveChanges();
        

    

在 (EF7) EF Core 中可以设置行为

感谢@Dabblernl 评论:http://blogs.msdn.com/b/adonet/archive/2015/10/15/ef7-beta-8-available.aspx#comments

迭戈 B 维加 [MSFT] 2015 年 10 月 17 日晚上 9:21 # @DabblerNL 是的,该功能已经在当前的夜间版本中实现。您必须使用 .OnDelete(DeleteBehavior.SetNull) 在模型中明确指定它。

上一个链接失效了。你可以在这里看到这个模型属性的描述:http://www.learnentityframeworkcore.com/conventions/one-to-many-relationship

【讨论】:

安慰一下:看来EF7终于支持On Delete Set Nullblogs.msdn.com/b/adonet/archive/2015/10/15/… 链接失效,看这里:learnentityframeworkcore.com/conventions/… 那是纯黑魔法,但谢谢。这救了我的培根。 不幸的是,它在 sql 数据库中创建了 n 个更新 sql。 (其中 n 是分配给 Person 的车辆数量)。是否可以生成一个更新? @zolty13 我认为已经清楚地解释了使用 EF 6.x 是不可能的,但使用 EF Core 是不可能的(它可以设置“ON DELETE”行为,这会在服务端自动发生) .如果您认为这个答案有用,请投票给它。【参考方案2】:

除了JotaBe的回答,还可以通过Include(...)来加载相关实体。

喜欢:

var person = ctx.Persons.Include(p => p.Vehicles).First();

Console.WriteLine("Deleting the person, and nullifying vehicles PersonId");
ctx.Persons.Remove(person);
ctx.SaveChanges();

【讨论】:

以上是关于删除可选相关实体时如何将 FK 更新为 null的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在实体框架中将表FK链接到另外两个PK?

如何克服死锁

将viewModel对象映射到ICollection实体

EF Code First:包括不处理可选关系

设置可选:必需的关系实体框架 - 流式 API

如何在不将实例模型添加到数据库的情况下向实体模型添加FK约束?