我在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”实体:< / p>
MyContext.Set<A>().Remove(MyA);
MyContext.SaveChanges();
SaveChanges将所有导航属性“B”设置为已删除的“A”实体为null。这使“B”无效,因为导航属性具有必需属性抛出异常“A”无法删除,因为“B”无效 - 这在某种程度上是一个奇怪的原因。
但是,在删除Navigation属性上的Required-Attribute并在B.A_ID属性上保留Required-Attribute后,将抛出正确的错误。
最后,在数据库异常之后,我最终得到一个对象图,其中“A”的所有导航属性都设置为“null”。
我认为这是EF的预期行为,但这会导致两个问题:
首先,删除操作无效。我还没有找到任何关于“删除”的信息 - 验证。数据注释仅考虑属性更改。
其次,如何在异常之后恢复已删除的实体,因为所有导航属性都设置为“null”。 EF 5关联不像SQL Server那样精确,它允许关联上的“无”,“设置为NULL”或“级联”。在“无”的情况下,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
答案 0 :(得分:1)
在删除A实体之前,只需删除所有B实体即可
[更新]
由于它是数据库优先,因此您的B类包含
public int A_ID { get; set; }
让我觉得这种关系并不像你描述的那样 尝试在数据库图表中查看。
答案 1 :(得分:0)
由于EF的这种行为,我使用DbContext为WPF应用程序使用了一种解决方法。出现此问题是因为我在窗口的生命周期中使用DbContext实例。因此,上下文中的对象图必须一致,直到窗口关闭。如果由于外键约束或由于数据库连接失败而导致删除操作失败,则在remove方法期间会更改对象图。 作为一种解决方法,我实现了以下方法:
在删除实体之前检查外键约束。 由于只有受加载的对象图表受到影响,因此只需检查已加载的实体。这并不完全令人满意,因为业务逻辑必须独立于已经知道所有约束的EF来实现此检查。
在单独的DbContext中删除实体 这样,如果删除失败,则不会更改原始上下文。这适用于窗口等效于已删除实体的情况。在这种情况下,如果删除操作成功,则窗口关闭,如果删除操作失败,则窗口将保持打开状态。但是,这种方法不包括删除子实体失败的情况。
最后,您可以在删除操作失败后刷新整个上下文,但在这种情况下,您将丢失之前设置的实体中的所有更改。