2017-10-31 4 views
0

これまで数回、私たちのシステムで数回実行しましたが、見つかりませんでした推論...EF 6:multipleを挿入する - 1つ以上の外部キーのプロパティがnullでないために関係を変更できませんでした

本質的に私は連絡先を持っており、連絡先にはnullable PhoneNumberがあります。

コードは、ビューモデルのリストをループし、それらをデータモデルにマップし、(すべてのモデルがマップされた後に)SaveChanges()を呼び出します。

テストデータには電話番号のデータが複製されています。テストデータを変更して連絡先ごとに異なる電話番号を設定すると、すべてがうまく保存されます。

ここでは、私が行っていることを単純なサンプルで説明します。

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ここ は、表の作成するスクリプトです(SQLが生成され、そのビット醜いそう):

問い合わせ

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 

電話番号

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 
+0

'PhoneNumbers'テーブルに重複する電話番号が入力されないようにする' Number'カラムに一意の制約があると仮定します。 'Contact'は整数フィールド' PhoneNumberId'(あるいはおそらく 'PhoneNumber_Id')を持っていますか?そうでなければ、EFはforiegnキーがnullではないことを認識せず、最初に連絡先を挿入しようとしている可能性があります。その後、電話番号 –

答えて

0

あなたのエンティティがマッピングされている方法に応じてとのPhoneNumberと連絡先との関係、あなたが期待するように、データを永続化することはできません。すべてのエンティティに対してデータベース生成IDを使用し、ナビゲーションプロパティを使用して関連エンティティを管理するのが理想的です。

例に基づいて、スキーマにContactのPhoneNumberId列があり、PhoneNumberテーブルのPKに対するFKであることを確認します。連絡先と電話番号の関係が正しく設定されていることも確認してください。あなたの連絡先エンティティがPhoneNumberId FKフィールドを宣言する場合

HasRequired(x => x.PhoneNumber) 
    .WithMany() 
    .HasForeignKey(x => x.PhoneNumberId); 

またはエンティティがFKフィールドを宣言しますが、ないかのインスタンスエンティティ型の構成やDbContext OnModelCreatingのいずれかのために、あなたのようなものを持っている必要がありますテーブルにはFKがあります

HasRequired(x => x.PhoneNumber) 
    .WithMany() 
    .Map(x => x.MapKey("PhoneNumberId")); 

複数の連絡先に対して同じ電話番号のレコードを使用することが可能であるため、意図されないことがあり、接触と電話番号の間に多対1の関係を形成しています。 (2人の連絡先が同じ電話番号を共有している可能性がありますが、1つの電話番号を変更すると必ずしも別の番号を更新する必要はありません)。1対1の関係の場合、PhoneNumberテーブルはContactIdをPK 。

この関係が適切に設定されていない場合、EFは必要なFKを認識せず、誤った順序でレコードを挿入する可能性があります。あなたのエンティティを取り込むという点で

、あなたはエンティティの作成とそれほど意図的と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(); 
    } 
} 

注意直接DbContextに渡します。 EFは適切な関係を使用してそれを管理し、FKを設定します。

これにより、関連するエンティティが一緒に挿入され、すべてのエンティティのDbSetをDbContextに追加することを避けることができます。 (最上位のエンティティとしてPhoneNumbersを検索する必要がありますか?)

しかし、何がうまくいかないかを完全に判断するには、コードの先頭の注釈か、最初にエンティティタイプのスキーマオーバーライドをOnModelCreatingに設定します。

+0

Contact.IDを割り当てないと空のGUID(すべてゼロ)を挿入しようとします。 foreach内の変更を保存しても、 "broken"リストはまだ失敗しています。元の投稿を更新して、SQLからテーブル作成コードを取得しました。私はphonenumberへの連絡のために1対1の関係を設定することについて詳しく見なければならないでしょう。問題を詳しく見てくれてありがとう! – THammons

+0

データベース生成ID列を使用するようにマッピングを設定します。これがコードファーストの場合、[DatabaseGenerated(DatabaseGeneratedOption.Identity)]属性。それはあなたのGuidを "NewId"するスキーマを作成します。私はスキーマファーストに大きく傾いており、インデックスがより親切であるため、NewSequentialId(SQL Server)で初期化するキー列を設定します。これらのマッピングをID生成列として構成する必要があります。私。 .HasKey(x => x.ContactID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); –

+0

アドバイスありがとうございます。残念なことに、これはデータベースであり、特にこれらのテーブルは、12年近く使用されており、多くのシステムで使用されています。このような状況では、コード・ファーストが可能性はないと私は考えています。 – THammons

関連する問題