[英]EF 6: inserting multiple - The relationship could not be changed because one or more of the foreign-key properties is non-nullable
I've run across this a few times in our system in the past, but haven't found the reasoning... 我过去在我们的系统中遇到过几次,但是没有找到理由……
In essence I have a Contact, and the contact has a nullable PhoneNumber. 本质上,我有一个联系人,并且该联系人具有可为空的PhoneNumber。
The code Loops through a list of View Models, maps them to the Data Models, then (after all models are mapped) calls SaveChanges(). 代码循环遍历视图模型列表,将它们映射到数据模型,然后(在映射所有模型之后)调用SaveChanges()。
The test data has the data for the phone number duplicated. 测试数据具有重复的电话号码数据。 When I alter the test data to have different phone numbers for each contact, everything saves fine. 当我将测试数据更改为每个联系人具有不同的电话号码时,一切都可以节省。
Here's a stripped down simple sample of what I'm doing that exhibits the behavior. 这是我正在做的事情的简化示例,展示了该行为。
class Program
{
static void Main(string[] args)
{
TestWeirdEDMXIssue();
Console.ReadLine();
}
static void TestWeirdEDMXIssue()
{
List<dynamic> works = new List<dynamic>
{
new { FirstName = "Fred", LastName = "Snider", PhoneNumber = "888-888-8881", CountryID = 1 },
new { FirstName = "Ted", LastName = "Snider", PhoneNumber = "888-888-8882", CountryID = 1 },
new { FirstName = "Mike", LastName = "Snider", PhoneNumber = "888-888-8883", CountryID = 1 },
new { FirstName = "Tim", LastName = "Snider", PhoneNumber = "888-888-8884", CountryID = 1 },
new { FirstName = "Todd", LastName = "Snider", PhoneNumber = "888-888-8885", CountryID = 1 },
new { FirstName = "Terry", LastName = "Snider", PhoneNumber = "888-888-8886", CountryID = 1 }
};
List<dynamic> broken = new List<dynamic>
{
new { FirstName = "Fred", LastName = "Snider", PhoneNumber = "888-888-8888", CountryID = 1 },
new { FirstName = "Ted", LastName = "Snider", PhoneNumber = "888-888-8888", CountryID = 1 },
new { FirstName = "Mike", LastName = "Snider", PhoneNumber = "888-888-8888", CountryID = 1 },
new { FirstName = "Tim", LastName = "Snider", PhoneNumber = "888-888-8888", CountryID = 1 },
new { FirstName = "Todd", LastName = "Snider", PhoneNumber = "888-888-8888", CountryID = 1 },
new { FirstName = "Terry", LastName = "Snider", PhoneNumber = "888-888-8888", CountryID = 1 }
};
TestWeirdEDMXIssueSave(works); //Completes with "Success!"
TestWeirdEDMXIssueSave(broken); //Throws Exception
}
static void TestWeirdEDMXIssueSave(List<dynamic> data)
{
try
{
using (var context = new DBEntities())
{
var creatorID = context.UserProfiles.FirstOrDefault(up => up.Contact.FirstName == "automationtestuser")?.ID ?? Guid.Empty;
foreach (var item in data)
{
var contact = context.Contacts.Create();
context.Contacts.Add(contact);
contact.ID = Guid.NewGuid();
contact.FirstName = item.FirstName;
contact.LastName = item.LastName;
contact.CreationDate = DateTime.Now;
contact.CreatedBy = creatorID;
var phoneNumber = context.PhoneNumbers.Create();
context.PhoneNumbers.Add(phoneNumber);
//PhoneNumber ID is Identity
phoneNumber.CreatedBy = creatorID;
phoneNumber.CreationDate = DateTime.Now;
phoneNumber.TypeID = (int)PhoneNumberTypes.Office;
phoneNumber.Number = item.PhoneNumber;
phoneNumber.CountryID = item.CountryID;
contact.PhoneNumber = phoneNumber;
}
context.SaveChanges();
}
Console.WriteLine("Success!");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
OUTPUT 输出值
Success!
System.InvalidOperationException: 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.
at System.Data.Entity.Core.Objects.ObjectContext.PrepareToSaveChanges(SaveOptions options)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
...(My Code)...
EDIT Here are the create scripts for the Tables (sql generated, so its a bit ugly): 编辑这里是表的创建脚本(生成的sql,因此有点难看):
Contact 联系
CREATE TABLE [Common].[Contacts](
[ID] [uniqueidentifier] NOT NULL,
[FirstName] [varchar](25) NOT NULL,
[MiddleInitial] [char](1) NULL,
[LastName] [varchar](50) NOT NULL,
[Title] [varchar](50) NULL,
[Description] [varchar](255) NULL,
[ContactEmailAddress] [varchar](100) NULL,
[ContactPhoneNumberID] [int] NULL,
[ContactFaxNumberID] [int] NULL,
[AddressID] [int] NULL,
[ACHDate] [datetime] NULL,
[CreatedBy] [uniqueidentifier] NOT NULL,
[CreationDate] [datetime] NOT NULL,
[IsMarkedAsDeleted] [bit] NOT NULL,
[Position] [varchar](50) NULL,
[MiddleName] [varchar](50) NULL,
[LastThenFirstName] AS (case when [LastName] IS NULL OR len(rtrim([LastName]))=(0) then [FirstName] when [FirstName] IS NULL OR len(rtrim([FirstName]))=(0) then [LastName] else (rtrim(ltrim([LastName]))+', ')+rtrim(ltrim([FirstName])) end) PERSISTED,
[FirstThenLastName] AS (rtrim(ltrim((isnull([FirstName],'')+' ')+isnull([LastName],'')))) PERSISTED,
[AuthenticatorPin] [int] NULL,
[AuthenticatorCode] [int] NULL,
CONSTRAINT [PK_Contacts] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [Common].[Contacts] ADD CONSTRAINT [DF_Contacts_ID] DEFAULT (newid()) FOR [ID]
GO
ALTER TABLE [Common].[Contacts] ADD CONSTRAINT [DF_Contacts_CreationDate] DEFAULT (getdate()) FOR [CreationDate]
GO
ALTER TABLE [Common].[Contacts] ADD CONSTRAINT [DF_Contacts_IsMarkedAsDeleted] DEFAULT ((0)) FOR [IsMarkedAsDeleted]
GO
ALTER TABLE [Common].[Contacts] WITH CHECK ADD CONSTRAINT [FK_Contacts_AddressID_Addresses_ID] FOREIGN KEY([AddressID])
REFERENCES [Common].[Addresses] ([ID])
GO
ALTER TABLE [Common].[Contacts] CHECK CONSTRAINT [FK_Contacts_AddressID_Addresses_ID]
GO
ALTER TABLE [Common].[Contacts] WITH CHECK ADD CONSTRAINT [FK_Contacts_ContactFaxNumberID_PhoneNumbers_ID] FOREIGN KEY([ContactFaxNumberID])
REFERENCES [Common].[PhoneNumbers] ([ID])
GO
ALTER TABLE [Common].[Contacts] CHECK CONSTRAINT [FK_Contacts_ContactFaxNumberID_PhoneNumbers_ID]
GO
ALTER TABLE [Common].[Contacts] WITH CHECK ADD CONSTRAINT [FK_Contacts_ContactPhoneNumberID_PhoneNumbers_ID] FOREIGN KEY([ContactPhoneNumberID])
REFERENCES [Common].[PhoneNumbers] ([ID])
GO
ALTER TABLE [Common].[Contacts] CHECK CONSTRAINT [FK_Contacts_ContactPhoneNumberID_PhoneNumbers_ID]
GO
ALTER TABLE [Common].[Contacts] WITH CHECK ADD CONSTRAINT [FK_Contacts_CreatedBy_UserProfiles_ID] FOREIGN KEY([CreatedBy])
REFERENCES [Common].[UserProfiles] ([ID])
GO
ALTER TABLE [Common].[Contacts] CHECK CONSTRAINT [FK_Contacts_CreatedBy_UserProfiles_ID]
GO
Phone Numbers 电话号码
CREATE TABLE [Common].[PhoneNumbers](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Number] [varchar](20) NOT NULL,
[Extension] [varchar](10) NULL,
[TypeID] [int] NOT NULL,
[CountryID] [int] NOT NULL,
[CreatedBy] [uniqueidentifier] NOT NULL,
[CreationDate] [datetime] NOT NULL,
[IsMarkedAsDeleted] [bit] NOT NULL,
CONSTRAINT [PK_PhoneNumbers] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [Common].[PhoneNumbers] ADD CONSTRAINT [DF_PhoneNumbers_CreationDate] DEFAULT (getdate()) FOR [CreationDate]
GO
ALTER TABLE [Common].[PhoneNumbers] ADD CONSTRAINT [DF_PhoneNumbers_IsMarkedAsDeleted] DEFAULT ((0)) FOR [IsMarkedAsDeleted]
GO
ALTER TABLE [Common].[PhoneNumbers] WITH CHECK ADD CONSTRAINT [FK_PhoneNumbers_CountryID_Countries_ID] FOREIGN KEY([CountryID])
REFERENCES [Common].[Countries] ([ID])
GO
ALTER TABLE [Common].[PhoneNumbers] CHECK CONSTRAINT [FK_PhoneNumbers_CountryID_Countries_ID]
GO
ALTER TABLE [Common].[PhoneNumbers] WITH CHECK ADD CONSTRAINT [FK_PhoneNumbers_CreatedBy_UserProfiles_ID] FOREIGN KEY([CreatedBy])
REFERENCES [Common].[UserProfiles] ([ID])
GO
ALTER TABLE [Common].[PhoneNumbers] CHECK CONSTRAINT [FK_PhoneNumbers_CreatedBy_UserProfiles_ID]
GO
ALTER TABLE [Common].[PhoneNumbers] WITH CHECK ADD CONSTRAINT [FK_PhoneNumbers_TypeID_PhoneNumberTypes_ID] FOREIGN KEY([TypeID])
REFERENCES [Common].[PhoneNumberTypes] ([ID])
GO
ALTER TABLE [Common].[PhoneNumbers] CHECK CONSTRAINT [FK_PhoneNumbers_TypeID_PhoneNumberTypes_ID]
GO
Depending on how your entities are mapped and the relationship between PhoneNumber and Contact, the data may not be persisting as you expect. 根据您的实体的映射方式以及PhoneNumber和Contact之间的关系,数据可能不会像您期望的那样持久。 Ideally you should use database generated IDs for all entities and use navigation properties to manage the related entities. 理想情况下,您应该为所有实体使用数据库生成的ID,并使用导航属性来管理相关实体。
Based on the examples, check that the schema has a PhoneNumberId column on Contact, and that it is a FK to the PhoneNumber table's PK. 根据示例,检查架构在Contact上是否具有PhoneNumberId列,并且是否为PhoneNumber表的PK的FK。 Also check that the relationship between contact and phone number is set up correctly. 还要检查联系人和电话号码之间的关系是否设置正确。 For instance either the entity type configuration or DbContext OnModelCreating you should have something like: 例如,实体类型配置或DbContext OnModelCreating您应该具有以下内容:
If your Contact entity declares a PhoneNumberId FK field: 如果您的联系人实体声明了PhoneNumberId FK字段:
HasRequired(x => x.PhoneNumber)
.WithMany()
.HasForeignKey(x => x.PhoneNumberId);
or if the entity does not declare the FK field, but the table has the FK: 或如果实体未声明FK字段,但表具有FK:
HasRequired(x => x.PhoneNumber)
.WithMany()
.Map(x => x.MapKey("PhoneNumberId"));
This forms a many-to-one relationship between contact and phone # which may not be intended since it is possible to use the same phone # record against multiple contacts. 这可能会在联系人和电话号码之间形成多对一关系,因为可能对多个联系人使用相同的电话号码记录,所以这可能是不希望的。 (2 contacts may share the same phone #, but changing the phone # on one should not necessarily update the # of the other.) For a 1-to-1 relationship the PhoneNumber table would best be served by using a ContactId as its PK. (2个联系人可以共享同一电话号码,但是在一个电话号码上更改电话号码不一定更新另一个电话号码。)对于一对一关系,最好通过使用ContactId作为其PK服务PhoneNumber表。 。
IF this relationship is not set up appropriately one way or the other, EF can end up inserting records in the wrong order, not recognizing the required FKs. 如果没有以一种或另一种方式适当地建立这种关系,则EF最终可能会以错误的顺序插入记录,而无法识别所需的FK。
In terms of populating your entities, you can be less deliberate with the entity creation and let EF manage the related data: 在填充实体方面,您可以减少实体创建的工作量,而让EF管理相关数据:
using (var context = new DBEntities())
{
// A better way to select the ID rather than returning the entire entity:
var creatorID = context.UserProfiles
.Where(up => up.Contact.FirstName == "automationtestuser")
.Select(up => up.ID)
.SingleOrDefault();
foreach (var item in data)
{
var contact = new Contact
{
//contact.ID = Guid.NewGuid(), Definitely recommend letting DB generate ID using NewSequentialId.
FirstName = item.FirstName,
LastName = item.LastName,
CreationDate = DateTime.Now,
CreatedBy = creatorID,
PhoneNumber = new PhoneNumber
{
CreatedBy = creatorID;
CreationDate = DateTime.Now;
TypeID = (int)PhoneNumberTypes.Office;
Number = item.PhoneNumber;
CountryID = item.CountryID;
}
}
context.Contacts.Add(contact);
context.SaveChanges();
}
}
Note that we don't need to create or add phone #'s directly to the DbContext. 请注意,我们不需要直接将电话#创建或添加到DbContext。 EF will take care of that using the appropriate relationships and set the FKs for us. EF将使用适当的关系来解决此问题,并为我们设置FK。
This ensures that the related entities are inserted together and you can avoid adding a DbSet for every entity into your DbContext. 这样可以确保将相关实体插入在一起,并且可以避免将每个实体的DbSet添加到DbContext中。 (Do you need to search for PhoneNumbers as a top level entity?) (您是否需要搜索PhoneNumbers作为顶级实体?)
However, to fully determine what is going wrong you should also post any mapping declarations you have, whether code-first annotations, or schema first with entity type configurations or OnModelCreating overrides. 但是,要完全确定问题出在哪里,您还应该发布任何映射声明,无论是代码优先的注释,还是使用实体类型配置或OnModelCreating替代的模式优先。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.