Introduction
I am currently working on a MVC and Entity Framework based project. After a few days I encountered a problem with Entity Framework when trying to delete an entity from a related data collection and received the following error:
“The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.”
Suppose I have the following entity data model. In this model the MasterTable entity has many DetailTable records. Here there is a one-to-many relationship between MasterTable and DetailTable. Now I want to remove all DetailTable entities related to the MasterTable where the MasterId value is 1.
Table Relationship in Entity diagram
![Table Relationship]()
Table Definition and Test Data
- CREATE TABLE [dbo].[MasterTable](
- [MasterId] [int] IDENTITY(1,1) NOT NULL,
- [Name] [varchar](50) NOT NULL,
- CONSTRAINT [PK_MasterTable] PRIMARY KEY CLUSTERED
- (
- [MasterId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
- GO
- CREATE TABLE [dbo].[DetailTable](
- [DetailId] [int] IDENTITY(1,1) NOT NULL,
- [MasterId] [int] NOT NULL,
- [Code] [varchar](50) NULL, [Name] [varchar](50) NULL,
- [Description] [varchar](50) NULL,
- CONSTRAINT [PK_DetailTable] PRIMARY KEY CLUSTERED
- (
- [DetailId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
- GO
- SET ANSI_PADDING OFF
- GO
-
- ALTER TABLE [dbo].[DetailTable] WITH CHECK ADD CONSTRAINT [FK_DetailTable_MasterTable] FOREIGN KEY([MasterId])
- REFERENCES [dbo].[MasterTable] ([MasterId])
- GO
- ALTER TABLE [dbo].[DetailTable] CHECK CONSTRAINT [FK_DetailTable_MasterTable]
- GO
- SET IDENTITY_INSERT [dbo].[MasterTable] O
-
- INSERT [dbo].[MasterTable] ([MasterId], [Name]) VALUES (1, N'test1')
- INSERT [dbo].[MasterTable] ([MasterId], [Name]) VALUES (2, N'test2')
- INSERT [dbo].[MasterTable] ([MasterId], [Name]) VALUES (3, N'test3')
-
- SET IDENTITY_INSERT [dbo].[MasterTable] OFF
-
- SET IDENTITY_INSERT [dbo].[DetailTable] ON
-
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (1, 1, N't1', N'name1', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (2, 1, N't2', N'name2', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (3, 1, N't3', N'name3', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (4, 2, N't4', N'name4', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (5, 2, N't5', N'name5', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (6, 2, N't6', N'name6', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (7, 3, N't7', N'name7', N'test')
- INSERT [dbo].[DetailTable] ([DetailId], [MasterId], [Code], [Name], [Description]) VALUES (8, 3, N't8', N'name8', N'test')
- SET IDENTITY_INSERT [dbo].[DetailTable] OFF
Sample code to delete detail record from the master entity
- static void Main(string[] args)
- {
- using (Entities context = new Entities())
- {
- var masterData = context.MasterTables.Include("DetailTables").Where(p => p.MasterId == 1 ).FirstOrDefault();
- var childData = masterData.DetailTables.ToList();
- foreach (var data in childData)
- {
- masterData.DetailTables.Remove(data);
- }
- context.SaveChanges();
- }
- }
Error snapshot
![error]()
Resolution
We have two ways to resolve the issue.
1. The first solution is to delete the child object (entity) from the object context or DB context. When we delete a DetailTable, the Entity Framework will automatically detach the DetailTable entity from any of its relationships and the DetailTable is to be marked as a deletion. I need to make some small changes in my code. First I need to delete the child data from the Context.
- static void Main(string[] args)
- {
- using (Entities context = new Entities())
- {
- var masterData = context.MasterTables.Include("DetailTables").Where(p => p.MasterId == 1 ).FirstOrDefault();
- var childData = masterData.DetailTables.ToList();
- foreach (var data in childData)
- {
- context.DetailTables.DeleteObject(data);
- }
- context.SaveChanges();
- }
- }
2. The second solution is a small change in the Primary Key of DetailTable. Here I include the Primary Key of the MasterTable (in other words MasterId) in a Primary Key of the detail table.
Update the model and my sample code works fine.
- IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[DetailTable]') AND name = N'PK_DetailTable')
- ALTER TABLE [dbo].[DetailTable] DROP CONSTRAINT [PK_DetailTable]
- GO
-
- ALTER TABLE [dbo].[DetailTable] ADD CONSTRAINT [PK_DetailTable] PRIMARY KEY CLUSTERED
- (
- [DetailId] ASC,
- [MasterId] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- GO