天天看點

防止Entity Framework重複插入關聯對象

Entity Framework在資料庫與對象映射上做了很多工作,除了将資料庫裡的表映射成相應的對象以外,它還能夠自動處理表之間的外鍵關系,并且可以用導航屬性(Navigation Property)的方式在對象層面上表示這些關系。

一般來說,當你插入一個對象時,Entity Framework預設會自動将對象通過導航屬性關聯的對象也插入到資料庫裡面去,大部分情況下,這是我們想要的結果。當然,如果關聯的對象已經存在于資料庫當中時,Entity Framework會避免重複插入對象。但問題是,這個檢查對象已經存在避免重複插入資料的功能,好像隻在一個Context(環境)下有效,即下面的代碼是可以正常執行的:

            using (var context = new TestContext())

            {

                var milestone = new Milestone()

                {

                    Title = "測試裡程碑",

                    StartDate = DateTime.Now,

                    DueDate = DateTime.Now + TimeSpan.FromDays(30)

                };

                context.Milestones.Add(milestone);

                context.SaveChanges();

                id = milestone.Id;

                var project = new Project()

                    Title = "測試項目",

                    DueDate = DateTime.Now + TimeSpan.FromDays(30),

                    Owner = "測試使用者"

                project.Children.Add(milestone);

                context.Project.Add(project);

            }

而如果對象是跨Context(環境)的話,或者基于現有對象複制的對象(包括主鍵也複制的情況),這就會産生重複插入的問題,因為新複制的對象,Entity Framework沒有辦法跟蹤對象的狀态,“誤以為”對象是一個全新的對象,比如,下面這段代碼就會導緻Entity Framework抛出一個異常,異常根據Entity對象的資料庫限制不同,可能會報告不同的錯誤資訊—這個問題一開始讓我迷惑了好幾天:

        public static void Main(string[] args)

        {

            int id = 0;

            using ( var context = new TestContext())

                var child = new Milestone() {

                    Id = id

                project.Children.Add(child);

        }

執行上面這段代碼,Entity Framework會在最後一個context.SaveChanges()上面抛出DbUpdateException,詳細資訊是:“{"The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.\r\nThe statement has been terminated."}”。這個異常一開始看上去太怪異了, 明明我将要儲存的Project對象的所有DateTime類型都已經指派(而且指派都在範圍内)了,為什麼還說超出指派範圍呢?

後面才發現,這是因為,Entity Framework在插入project對象是,看到它的關聯對象清單Children裡,有一個Milestone對象,而Milestone對象是重新複制的(隻複制了ID)—這個場景是因為使用者在網頁上建立一個項目時,可以從裡程碑清單裡選擇一個事先建立好了的裡程碑。由于Entity Framework沒有辦法跟蹤這個新複制的Milestone對象的狀态,是以它“誤認為”這個對象是一個新的對象,是以重新插入這個對象,而這個對象又沒有設定一些必要的日期屬性,導緻了前面那個異常。

既然搞明白了道理,修複起來也很簡單,就是顯式告訴Entity Framework跟蹤這個對象—通過把第二個using段改成下面這樣:

                    Id = id

                var adapter = context as IObjectContextAdapter;

                adapter.ObjectContext.AttachTo("Milestones", child);

注意:我用的是Entity Framework CTP 5,采用的是代碼優先(code first)的方式建立的資料庫,但是本文提到的問題在資料庫優先和模型優先的情況裡都是一樣的。

因為在網上找了好多文章都沒有提到這個問題,是以在這裡記錄下來。

本文轉自 donjuan 部落格園部落格,原文連結: http://www.cnblogs.com/killmyday/archive/2010/12/17/1909630.html  ,如需轉載請自行聯系原作者