无法删除需要父属性的ef实体

时间:2014-05-03 21:51:32

标签: c# entity-framework entity-framework-6

我对EF并不是很棒,所以也许这很简单。

我有

    public void DeleteLicense(int licenseId)
    {
        var entityToDelete = context.Licenses.Find(licenseId);
        context.Licenses.Remove(entityToDelete);
    }

我检查过它找到了正确的许可证,上下文是一个ninject(每个请求一个)DbContext,

但是在运行上面的函数后,我在上下文中调用SaveChanges()时出现了一个奇怪的错误。我得到:“客户名称字段是必需的。

现在这很奇怪,因为客户名在账户(不是许可证)中,但他们仍在联系。接下来是更多内容:

我的帐户实体

[Required]
public String CustomerName { get; set; }

public virtual ICollection<License> Licenses { get; set; }
...

我的许可实体

public virtual Account Account { get; set; }
...

我的流畅设置

modelBuilder.Entity<Account>().HasMany(x => x.Licenses)
.WithRequired(x => x.Account).WillCascadeOnDelete(false);

我不明白,因为即使有一个失败的约束,那么为什么缺少CustomerName。当我删除许可证并且之前设置了CustomerName时,我不会触摸CustomerName。

更新

所以这里是代码中的更多细节。就我所见,完整的执行路径是

下面的

DeleteLicenseAPI 接听电话,ID是正确的,它会传递给私人电话。 私有函数调用显示在问题顶部附近的 DeleteLicense 提交()仅调用 context.SaveChanges();

public ActionResult DeleteLicenseAPI(int licenseId)
{
        if (DeleteLicense(licenseId))
        {
            return Content("ok");
        }

        return Content("[[[Failed to delete license]]]");

}

private bool DeleteLicense(int licenseId)
{
        //todo: sort out busniess rules for delete, is cascaded?
        _accountRepository.DeleteLicense(licenseId);
        _accountRepository.Commit();

        return true;
}

_accountRepository 看起来像这样

public class EFAccountRepository : EntityFrameworkRepository<Account>
, IAccountRepository

public EFAccountRepository(EvercateContext context) : base(context)
{       
}

这是Ninject中的代码,它将所有内容设置为

kernel.Bind<EvercateContext>()
.To<EvercateContext>()
.InRequestScope()
.WithConstructorArgument("connectionStringOrName", "EvercateConnection");


kernel.Bind<IAccountRepository>().To<EFAccountRepository>();

所以即使我使用工作单元尽可能地看到(并且它不应该)在运行SaveChanges之前在此请求中没有调用任何其他内容。 有没有办法看看DbContext将在SaveChanges上做什么,而不实际运行该方法(因为它抛出 DbEntityValidationException

4 个答案:

答案 0 :(得分:2)

我可以想象如果你在Account构造函数中初始化License导航属性,可能会发生这种奇怪的异常:

public License
{
    Account = new Account();
}

致电时的流程......

var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);

......那么可能是:

  • License实体被加载(没有导航属性Account)并附加到上下文(状态Unchanged
  • 构造函数设置Account导航属性,但不会附加(状态Detached
  • 当您致电Remove License实体DetectChanges由EF内部调用时。它检测到License.Account正在引用分离的实体并将其附加到上下文(状态Added)。 License的状态更改为Deleted
  • 当您致电SaveChanges时,更改跟踪器会找到两个实体:状态License中的Deleted和州Account中的Added
  • 验证运行并发现应该插入数据库的实体CustomerName的必需属性Accountnull(因为只有{{1}的默认构造函数被称为。)。
  • 抛出验证异常。

我不确定细节是否正确,但可能会发生类似的情况。

在任何情况下,您都应该从Account构造函数中删除Account = new Account();,并检查您是否在代码库中的实体构造函数中初始化其他引用导航属性。 (初始化空导航集合是可以的。)这是众所周知的难以发现和理解的奇怪问题的常见来源。

答案 1 :(得分:0)

我尝试按照建议重写SaveChanges。 当我这样做时,我发现了一个即将删除的许可证(应该如此),但我还找到了一个即将创建的帐户。

我更改了DeleteLicense,如下所示。

    public void DeleteLicense(int licenseId)
    {
        var entityToDelete = context.Licenses.Find(licenseId);
        entityToDelete.Account = null;
        context.Licenses.Remove(entityToDelete);
    }

然后代码就可以了。许可证已删除,帐户仍然存在,但未创建新帐户。

但为什么,我根本不明白为什么。 这是我用流畅的api设置的关系吗?

答案 2 :(得分:0)

In my case this happened because my entity had a [Required] property that was of type int? which made it nullable. While inspecting the model that came back from the db I saw the property had a value but the entity that ended up being saved to the database had that value stripped during SaveChanges for some reason. When I switched the value to the expected int type all worked just fine. :shrug:

答案 3 :(得分:0)

我有一个类似的问题,对我来说,我好像没有正确建立父母和孩子在各自班级之间的关系。

我的解决方法是将以下指定的属性添加到Child类中,以表示代表其父级ID的属性

public class Child
{
    [Key, Column(Order = 1)]
    public string Id { get; set; }

    [Key, ForeignKey("Parent"), Column(Order = 2)]  // adding this line fixed things for me
    public string ParentId {get; set;}
}

public class Parent
{
    [Key, Column(Order = 1)]
    public string Id { get; set; }

    ...

    public virtual ICollection<Child> Children{ get; set; }
}