Entity Framework 6 在从绑定的 DataGridView 删除时应该使用 DELETE 时使用 UPDATE

Posted

技术标签:

【中文标题】Entity Framework 6 在从绑定的 DataGridView 删除时应该使用 DELETE 时使用 UPDATE【英文标题】:Entity Framework 6 uses an UPDATE when it should be using a DELETE when deleting from a bound DataGridView 【发布时间】:2014-03-09 23:49:35 【问题描述】:

我有一个DataGridView 绑定到一个BindingSource 绑定到DataMember tbl_Distribution_Orders_RestrictionDataSource bs_tbl_Series_Manufacturer,它本身就是一个BindingSource,它有一个链接到一个实体的DataSourceForeNET.tbl_Series_Manufacturer。有问题的DataGridView 仅显示与bs_tbl_Series_Manufacturer 的当前记录相关的记录。

tbl_Distribution_Orders_Restriction 有如下定义:

CREATE TABLE [Fore].[tbl_Distribution_Orders_Restriction](
    [GM_ORDER_NBR] [varchar](50) NOT NULL,
    [Include] [bit] NOT NULL,
    [AUS_SRS_CD] [varchar](2) NULL,
    [ManufacturerID] [tinyint] NULL,
    [CNTLG_DLR_CD] [varchar](6) NULL,
    [FAWCode] [varchar](15) NULL,
 CONSTRAINT [PK_tbl_Distribution_Orders_Restriction_1] PRIMARY KEY CLUSTERED 
([GM_ORDER_NBR] ASC) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction]  WITH CHECK ADD  CONSTRAINT [FK_tbl_Distribution_Orders_Restriction_tbl_Dealer] FOREIGN KEY([CNTLG_DLR_CD])
REFERENCES [Fore].[tbl_Dealer] ([cntlg_dlr_cde])

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction] CHECK CONSTRAINT [FK_tbl_Distribution_Orders_Restriction_tbl_Dealer]

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction]  WITH CHECK ADD  CONSTRAINT [FK_tbl_Distribution_Orders_Restriction_tbl_ModelCodes] FOREIGN KEY([FAWCode])
REFERENCES [Fore].[tbl_ModelCodes] ([FAWCode])

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction] CHECK CONSTRAINT [FK_tbl_Distribution_Orders_Restriction_tbl_ModelCodes]

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction]  WITH CHECK ADD  CONSTRAINT [FK_tbl_Distribution_Orders_Restriction_tbl_Series_Manufacturer] FOREIGN KEY([AUS_SRS_CD], [ManufacturerID])
REFERENCES [Fore].[tbl_Series_Manufacturer] ([aus_series_cde], [ManufacturerID])

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction] CHECK CONSTRAINT [FK_tbl_Distribution_Orders_Restriction_tbl_Series_Manufacturer]

ALTER TABLE [Fore].[tbl_Distribution_Orders_Restriction] ADD  CONSTRAINT [DF_tbl_Distribution_Orders_Restriction_Include]  DEFAULT ((0)) FOR [Include]

CREATE TRIGGER [Fore].[trg_I_tbl_Distribution_Orders_Restriction] 
   ON  [Fore].[tbl_Distribution_Orders_Restriction]
   INSTEAD OF INSERT
AS 
BEGIN
    SET NOCOUNT ON
    INSERT INTO Fore.tbl_Distribution_Orders_Restriction (GM_ORDER_NBR, [Include], AUS_SRS_CD, ManufacturerID, CNTLG_DLR_CD, FAWCode)
    SELECT      inserted.GM_ORDER_NBR, inserted.Include, Fore.qry_SOM_OrderInfo.AUS_SRS_CD, Fore.tbl_ModelCodes.ManufacturerID, 
                      Fore.qry_SOM_OrderInfo.CNTLG_DLR_CD, Fore.qry_SOM_OrderInfo.FAWCode
    FROM        inserted INNER JOIN
                      Fore.qry_SOM_OrderInfo WITH (NOEXPAND) ON inserted.GM_ORDER_NBR = Fore.qry_SOM_OrderInfo.GM_ORDER_NBR INNER JOIN
                      Fore.tbl_ModelCodes ON Fore.qry_SOM_OrderInfo.FAWCode = Fore.tbl_ModelCodes.FAWCode
END

CREATE TRIGGER [Fore].[trg_U_tbl_Distribution_Orders_Restriction] 
   ON  [Fore].[tbl_Distribution_Orders_Restriction]
   INSTEAD OF UPDATE
AS 
BEGIN
    SET NOCOUNT ON
        DELETE Fore.tbl_Distribution_Orders_Restriction
    FROM deleted INNER JOIN Fore.tbl_Distribution_Orders_Restriction ON Fore.tbl_Distribution_Orders_Restriction.GM_ORDER_NBR=deleted.GM_ORDER_NBR AND Fore.tbl_Distribution_Orders_Restriction.[Include]=deleted.[Include]
INSERT INTO Fore.tbl_Distribution_Orders_Restriction (GM_ORDER_NBR, [Include], AUS_SRS_CD, ManufacturerID, CNTLG_DLR_CD, FAWCode)
    SELECT  inserted.GM_ORDER_NBR, inserted.Include, qry_SOM_OrderInfo.AUS_SRS_CD, Fore.tbl_ModelCodes.ManufacturerID, Fore.qry_SOM_OrderInfo.CNTLG_DLR_CD, Fore.qry_SOM_OrderInfo.FAWCode
    FROM    inserted INNER JOIN Fore.qry_SOM_OrderInfo WITH (NOEXPAND) 
                ON inserted.GM_ORDER_NBR = Fore.qry_SOM_OrderInfo.GM_ORDER_NBR INNER JOIN
                      Fore.tbl_ModelCodes ON Fore.qry_SOM_OrderInfo.FAWCode = Fore.tbl_ModelCodes.FAWCode
END

我的问题是这样的:

当我从DataGridView 中删除一行时,该行会按预期消失。此时,我的事件代码调用Context.SaveChanges() 但是,该行并没有从数据库中删除,并且下次我打开表单时,我认为我已经删除的所有记录仍然存在。任何时候都不会显示错误消息。

我将Context.Database.Log绑定到调试窗口,发现当我通过DataGridView执行delete时,执行Context.SaveChanges()会产生类似如下日志输出的结果:

UPDATE [Fore].[tbl_Distribution_Orders_Restriction]
SET [AUS_SRS_CD] = NULL, [ManufacturerID] = NULL
WHERE ([GM_ORDER_NBR] = @0)

-- @0: '6W2CAA' (Type = AnsiString, Size = 50)

-- Executing at 12/02/2014 8:15:51 AM +11:00

-- Completed in 142 ms with result: 1

此 SQL 语句的最终结果是,对 [Fore].[tbl_Distribution_Orders_Restriction] 中的“6W2CAA”记录没有任何更改,因为该表具有填充字段 [AUS_SRS_CD][ManufacturerID](以及其他)的触发器,基于[GM_ORDER_NBR] 值,前一个字段的存在只是为了消除对必须从许多其他表中查找这些值的不可接受的慢存储过程的需要 - 将查找负载转移到 SQL Server 上更可接受插入/更新。

但是,即使这些触发器不存在,如果用户删除然后插入具有相同 [GM_ORDER_NBR] 值的记录,也会发生错误,因为具有相同(主键)[GM_ORDER_NBR] 值的记录会仍然存在,尽管 [AUS_SRS_CD][ManufacturerID] 值为 NULL。

我原以为实体框架在从 DataGridView 删除后执行的 SQL 语句更像:DELETE FROM [Fore].[tbl_Distribution_Orders_Restriction] WHERE ([GM_ORDER_NBR] = @0)

当我在事件代码中.Remove 一个实体,然后是.SaveChanges,无效的UPDATE 仍然首先出现,然后是DELETE

如何让实体框架执行正确的DELETE SQL 语句以响应DataGridView 删除,而不是(而不是补充)完全不适当的UPDATE

【问题讨论】:

你的数据源允许删除吗? 数据源允许删除 - 我可以使用 SQL 删除相关表,也可以使用 EF 删除其他表。 您是否尝试过使用 EF 在不通过 DataGridView 的情况下删除一行? 我可以.Remove一个实体,结果是.Savechanges上的SQL记录被删除 这会让我认为问题出在 DataGridView,而不是 EF。 【参考方案1】:

实体框架不会为关系发出“删除”,您的 DataGridView 绑定到实体集合而不是实体集。

当一个实体从 EntityCollection 中删除时,Entity Framework 只会删除它的由外键标识的关联或关系。 Entity Framework 不知道是否将其从数据库中删除。

考虑一个具有多个外键的实体。

 Table Products
    PK ProductID
    FK CategoryID (nullable)
    FK VendorID (nullable)

假设产品与类别表和供应商表相关。现在,当您从一个类别中删除产品时,不必将其从表本身中删除。因为有些产品可能有类别而有些可能没有,同样有些可能有供应商定义,有些可能没有。

这就是 EF 不删除实际记录的原因,它只是将其外键列设置为空。这会从其关联的相关表中删除一个实体,但实体仍然存在。

如果外键不可为空,则旧版本的 EF 会引发错误。我还不知道新的。

理论上,在可以为空的外键的情况下,子实体可以在没有其父实体的情况下存在,而在不可为空的外键的情况下,子实体不能在没有其父实体的情况下存在,在这种情况下应该正确定义关系。我已经覆盖了一个 SaveChanges 方法来识别不可为空的外键,如果它的关联父级设置为空,则相应地删除记录。

回答

您可以将 DGV 绑定到 tbl_Distribution_Orders_Restriction 的实体集并为ForeginKey = Manufacturer DGV's SelectedItem.PrimaryKey 设置条件/谓词

【讨论】:

感谢您的理论。但是我的问题是:如何在 DGV tbl_Distribution_Orders_Restriction 记录中显示与另一个 DGV 中当前显示的 tbl_Series_Manufacturer 记录相关的记录,并允许用户使用最少的代码添加/编辑/删除 tbl_Distribution_Orders_Restriction 记录,或者在我的前端还是在数据库中? 您必须将 DGV 绑定到 EntitySet,条件包括与相关实体的主键进行外键比较。是一对多还是多对多? Akash Kava,您的回答对问题发生的原因有很多解释,但对如何纠正问题的解释却很短。您如何如何“为ForeginKey = Manufacturer DGV's SelectedItem.PrimaryKey 设置条件/谓词”?绑定到实体的BindingSource 似乎无法接受过滤器,并且我无法在实体的映射详细信息中添加条件,因为这是一个常量。【参考方案2】:

Akash Kava 的回答很有启发性,它揭示了在我所描述的情况下,记录是未链接的,而不是在“删除”时删除。但是,他的“答案”还不够充分,是的,您可以“将 DGV 绑定到 tbl_Distribution_Orders_Restriction 的实体集并为 ForeginKey = Manufacturer DGV's SelectedItem.PrimaryKey 设置条件/谓词”,但是,这样做,您将失去 EF 跟踪以及完全更改底层数据库数据的能力。

但是,Akash Kava 的回答是一个有用的开始,所以我并没有简单地否决它。

我找到的解决方案是:

加载tbl_Distribution_Orders_Restriction的数据,过滤到绑定到BindingSourceObservableCollection,然后为ObservableCollectionCollectionChanged事件添加一个handler,其中ObservableCollection的变化是传播回源,它们将从那里传播回m_Context.SaveChanges上的数据库:

Dim m_oc_tDSR = New ObjectModel.ObservableCollection(Of tbl_Distribution_Stock_Restriction)(From tDSR As tbl_Distribution_Stock_Restriction In m_Context.tbl_Distribution_Stock_Restriction.Local
                                                                                        Where tDSR.AUS_SRS_CD = m_CurrentSeries And tDSR.ManufacturerID = m_CurrentManufacturer
                                                                                        Select tDSR)
bs_tbl_Distribution_Stock_Restriction.DataSource = m_oc_tDSR
bs_tbl_Distribution_Stock_Restriction.ResetBindings(False)
AddHandler m_oc_tDSR.CollectionChanged, AddressOf m_oc_tDSR_CollectionChanged

m_oc_tDSR_CollectionChanged 的​​定义在哪里(本质上):

Private Sub m_oc_tDOR_CollectionChanged(sender As Object, e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
    Select Case e.Action
        Case Specialized.NotifyCollectionChangedAction.Add
            For Each tDOR As tbl_Distribution_Orders_Restriction In e.NewItems
                m_Context.tbl_Distribution_Orders_Restriction.Add(tDOR)
            Next
        Case Specialized.NotifyCollectionChangedAction.Remove
            For Each tDOR As tbl_Distribution_Orders_Restriction In e.OldItems
                Try
                    m_Context.tbl_Distribution_Orders_Restriction.Remove(tDOR)
                Catch ex As InvalidOperationException
                End Try
            Next
        Case Specialized.NotifyCollectionChangedAction.Replace
            For CtrA As Long = 0 To e.OldItems.Count - 1
                m_Context.tbl_Distribution_Orders_Restriction.Remove(DirectCast(e.OldItems, tbl_Distribution_Orders_Restriction))
                m_Context.tbl_Distribution_Orders_Restriction.Add(DirectCast(e.NewItems, tbl_Distribution_Orders_Restriction))
            Next
    End Select
End Sub

这允许过滤的实体集仍然被跟踪。

【讨论】:

以上是关于Entity Framework 6 在从绑定的 DataGridView 删除时应该使用 DELETE 时使用 UPDATE的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Entity Framework 将多个表中的数据绑定到 datagridview 并使用 CRUD 操作?

C#,Winform绑定实体框架(Entity Framework)的实体,如何去掉或隐藏导航属性?

Entity Framework 6 暂时禁用拦截

Entity Framework 6 开发系列 目录

Entity Framework Core 6.0 预览4 性能改进

添加 [DataContract] 到 Entity Framework 6.0 POCO Template