1

我在 WPF 应用程序中将 EF5 与 DbContext 和 Database-First 方法一起使用,并在以下场景中删除实体和使用所需的数据注释属性时遇到了一些逻辑问题:

这是两个用外键相互引用的表,没有级联删除:

     |----A----|    |----B----| 
     |ID   int |<-| |ID   int |
     |---------|  |-|A_ID int |

因此,如果某些“B”引用“A”,则不能删除“A”。

EF 5 模型包括关联,并且 - 由于没有设置级联 - OnDelete 设置为“无”到关联的两端。我已经用 [Required] 属性装饰了“A_ID”字段和导航属性“BA”——当我删除一个“A”实体时,我的麻烦就开始了,其中“B”实体引用了这个“A”实体:

  MyContext.Set<A>().Remove(MyA);
  MyContext.SaveChanges();

SaveChanges 将“B”的所有导航属性设置为已删除的“A”实体为空。这会使“B”无效,因为导航属性具有必需的属性抛出异常,即“A”不能被删除,因为“B”无效 - 这在某种程度上是一个奇怪的原因。

但是,在删除 Navigation 属性上的 Required-Attribute 并将 Required-Attribute 保留在 B.A_ID 属性上之后,会引发正确的错误。

最后,在数据库异常之后,我最终得到一个对象图,其中“A”的所有导航属性都设置为“null”。

我认为这是 EF 的预期行为,但这会导致两个问题:

首先,删除操作无效。我还没有找到任何关于“删除”验证的信息。数据注释仅考虑属性更改。

其次,由于所有导航属性都设置为“null”,因此如何在异常后恢复已删除的实体。EF 5 关联不如允许关联上“无”、“设置为 NULL”或“级联”的 SQL Server 关联精确。在“无”的情况下,SQL Server 抛出异常,使所有实体保持不变。

如果有人在数据库异常后遇到“CanDelete”验证和实体恢复的相同问题并有解决方案或可以将我指向其他相关线程,请告诉我。

你的,

马库斯

示例类:

// Entity to delete
public partial class A {
    public A() {
        this.Bs = new HashSet<B>();
    }

    public int ID { get; set; }
    public Nullable<int> C_ID { get; set; }
    public string Name { get; set; }

    public virtual C C { get; set; }
    public virtual ICollection<B> Bs { get; set; }
}

// Child entities of A with foreign key constraint
public partial class B {
    public int ID { get; set; }
    public int A_ID { get; set; }
    public string Name { get; set; }

    public virtual A A { get; set; }
}

// Example class of an additional entity referencing A
public partial class C {
    public C() {
        this.As = new HashSet<A>();
    }

    public int ID { get; set; }
    public string Name { get; set; }

    public virtual ICollection<A> As { get; set; }
}

数据库架构:

CREATE DATABASE [EFABC]
GO
USE [EFABC]
GO
CREATE TABLE [dbo].[tA](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [C_ID] [int] NULL,
    [Name] [nvarchar](50) NULL,
 CONSTRAINT [PK_tA] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[tB](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [A_ID] [int] NOT NULL,
    [Name] [nvarchar](50) NULL,
 CONSTRAINT [PK_tB] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[tC](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL,
 CONSTRAINT [PK_tC] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[tA]  WITH CHECK ADD  CONSTRAINT [FK_tA_tC] FOREIGN KEY([C_ID])
REFERENCES [dbo].[tC] ([ID])
GO
ALTER TABLE [dbo].[tA] CHECK CONSTRAINT [FK_tA_tC]
GO
ALTER TABLE [dbo].[tB]  WITH CHECK ADD  CONSTRAINT [FK_tB_tA] FOREIGN KEY([A_ID])
REFERENCES [dbo].[tA] ([ID])
GO
ALTER TABLE [dbo].[tB] CHECK CONSTRAINT [FK_tB_tA]
GO
4

2 回答 2

1

只需在删除 A 实体之前删除所有 B 实体 [更新]
由于它是数据库优先,因此您的 B 类包含

  public int A_ID { get; set; }

让我觉得这种关系并不像你描述的那样。
尝试查看数据库图表。

于 2013-06-02T20:10:48.980 回答
0

由于这种行为是 EF 的意图,我使用 DbContext 为 WPF 应用程序使用了一种解决方法。出现问题是因为我在窗口的生命周期中使用了 DbContext 实例。因此,上下文中的对象图必须保持一致,直到窗口关闭。在删除操作失败的情况下,无论是由于外键约束还是由于数据库连接失败,对象图都会在删除方法期间更改。作为一种解决方法,我实施了以下方法:

  1. 在删除实体之前检查外键约束。由于只有加载的对象图受到影响,因此必须检查加载的实体。这并不完全令人满意,因为业务逻辑必须独立于已经知道所有约束的 EF 来实现此检查。

  2. 在单独的 DbContext 中删除实体 这样,如果删除失败,原始上下文不会更改。这适用于窗口等同于已删除实体的情况。在这种情况下,如果删除操作成功,则窗口将关闭,如果删除操作失败,则窗口会以其原始上下文保持打开状态。但是,该方法不涵盖删除子实体失败的情况。

最后,您可以在删除操作失败后刷新整个上下文,但在这种情况下,您将丢失之前设置的实体中的所有更改。

于 2013-06-06T22:16:30.203 回答