【问题标题】:Fail to remove ef entity with parent property required无法删除需要父属性的 ef 实体
【发布时间】:2014-06-20 10:34:29
【问题描述】:

我对 EF 并不出色,所以也许它很容易。

我有

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

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

但是当我在运行上述函数后在上下文中调用 SaveChanges() 时遇到一个奇怪的错误。我得到:“CustomerName 字段是必需的。

现在这很奇怪,因为 CustomerName 在 Account(不是 License)中,它们是链接的,但仍然存在。所以这里还有一些:

我的帐户实体

[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 是正确的,它传递给一个私有函数。 私有函数调用显示在问题顶部附近的 DeleteLicenseCommit() 只调用 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

【问题讨论】:

  • 似乎没有意义。这是DeleteLicense 方法的一部分,您可能会在其中无意中创建Account 吗?
  • 很高兴知道这不仅仅是我。我更新了问题中的更多细节。但总结是:我可以看到不运行 SaveChanges 会做什么吗?
  • 您可以在DbContext 中覆盖SaveChanges 并检查this.ChangeTracker.Entries() 的内容。

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


【解决方案1】:

我可以想象,如果您在 License 构造函数中初始化 Account 导航属性,如下所示:

public License
{
    Account = new Account();
}

调用时的流程...

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

...那么可能是:

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

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

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

【讨论】:

  • 我们每天都在学习新东西。非常感谢您的出色回答!
  • Slauma,我可以借用你的水晶球几天吗? :)
  • @GertArnold:他自己的回答基本上是说 entityToDelete.Account 是在更改跟踪器中找到的,没有水晶球也可以猜测 :)
【解决方案2】:

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

我更改了 DeleteLicense,如下所示。

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

代码立即生效。许可证被删除,帐户仍然存在,但没有创建新帐户。

但是为什么,我完全不明白为什么。 是不是我用fluent api设置的关系?

【讨论】:

  • 读到这里我怀疑你的License 实体构造函数中有一个Account = new Account();。由于Remove 内部发生的自动更改检测,当您调用...Remove(...) 时,此Account 可能会被添加到上下文中(状态为Added)。因为 Account 仅使用默认构造函数创建 CustomerName 可能是 null 导致验证异常。在构造函数中删除该行(假设它确实存在)。
  • 太棒了!!而已!如果你把它作为一个答案,我可以接受这个问题。
【解决方案3】:

在我的例子中,发生这种情况是因为我的实体有一个 [Required] 属性,该属性的类型为 int?,这使得它可以为空。在检查从数据库返回的模型时,我看到该属性有一个值,但最终保存到数据库的实体由于某种原因在SaveChanges 期间删除了该值。当我将值切换到预期的int 类型时,一切正常。 :耸肩:

【讨论】:

    【解决方案4】:

    我遇到了类似的问题,对我来说,似乎我没有正确建立各自班级中父母和孩子之间的关系。

    我的解决方法是将下面指定的属性添加到 Child 类中,用于表示其 Parent 的 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; }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-02
      • 1970-01-01
      • 2021-07-22
      相关资源
      最近更新 更多