2011-08-15 117 views
3

下面是一個示例場景,說明我遇到的問題。EF 4.1代碼優先:爲什麼EF不設置此導航屬性?

這裏是DB腳本生成的SQL 2008數據庫:

USE [master] 
GO 

/****** Object: Database [EFTesting] Script Date: 08/15/2011 09:56:33 ******/ 
CREATE DATABASE [EFTesting] ON PRIMARY 
(NAME = N'EFTesting', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\EFTesting.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB) 
LOG ON 
(NAME = N'EFTesting_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\EFTesting_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%) 
GO 

ALTER DATABASE [EFTesting] SET COMPATIBILITY_LEVEL = 100 
GO 

USE [EFTesting] 
GO 

/****** Object: Table [dbo].[Schedule] Script Date: 08/15/2011 09:45:53 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[Schedule](
    [ScheduleID] [int] IDENTITY(1,1) NOT NULL, 
    [Name] [nvarchar](50) NOT NULL, 
    [Version] [timestamp] NOT NULL, 
CONSTRAINT [PK_Schedule] PRIMARY KEY CLUSTERED 
(
    [ScheduleID] 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 

/****** Object: Table [dbo].[Customer] Script Date: 08/15/2011 09:45:53 ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE TABLE [dbo].[Customer](
    [CustomerID] [int] IDENTITY(1,1) NOT NULL, 
    [Name] [nvarchar](50) NOT NULL, 
    [ScheduleID] [int] NOT NULL, 
    [Version] [timestamp] NOT NULL, 
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [CustomerID] 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 

/****** Object: ForeignKey [FK_Customer_Schedule] Script Date: 08/15/2011 09:45:53 ******/ 
ALTER TABLE [dbo].[Customer] WITH CHECK ADD CONSTRAINT [FK_Customer_Schedule] FOREIGN KEY([ScheduleID]) 
REFERENCES [dbo].[Schedule] ([ScheduleID]) 
GO 
ALTER TABLE [dbo].[Customer] CHECK CONSTRAINT [FK_Customer_Schedule] 
GO 

這裏是模型,上下文中的C#代碼和測試工具:

using System.ComponentModel.DataAnnotations; 
using System.Data.Entity; 
using System.Diagnostics; 
using System.Linq; 

namespace Tester 
{ 
    public class Context : DbContext 
    { 
     public Context(string connectionString) : base(connectionString) 
     { 
      Configuration.LazyLoadingEnabled = false; 
      Configuration.ProxyCreationEnabled = false; 
     } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      // Customer 
      modelBuilder.Entity<Customer>() 
       .HasKey(c => c.ID) 
       .Property(c => c.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("CustomerID"); 

      modelBuilder.Entity<Customer>() 
       .Property(c => c.Version).IsConcurrencyToken(); 

      modelBuilder.Entity<Customer>() 
       .HasRequired(c => c.Schedule); 

      modelBuilder.Entity<Customer>() 
       .ToTable("Customer"); 

      // Schedule 
      modelBuilder.Entity<Schedule>() 
       .HasKey(s => s.ID) 
       .Property(s => s.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("ScheduleID"); 

      modelBuilder.Entity<Schedule>() 
       .Property(s => s.Version).IsConcurrencyToken(); 

      modelBuilder.Entity<Schedule>() 
       .ToTable("Schedule"); 
     } 
    } 

    public class Customer 
    { 
     public Customer() 
     { 
      Schedule = new Schedule(); 
     } 

     public int ID { get; set; } 

     public string Name { get; set; } 

     public int ScheduleID { get; set; } 

     public Schedule Schedule { get; set; } 

     public byte[] Version { get; set; } 
    } 

    public class Schedule 
    { 
     public int ID { get; set; } 

     public string Name { get; set; } 

     public byte[] Version { get; set; } 
    } 

    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      // create new customer/schedule 
      var context = new Context(@"Data Source=.\SQLEXPRESS;Initial Catalog=EFTesting;Integrated Security=True;MultipleActiveResultSets=True"); 
      var customer = new Customer 
      { 
       Name = "CUSTOMER", 
       Schedule = new Schedule 
       { 
        Name = "SCHEDULE" 
       } 
      }; 

      context.Set<Customer>().Add(customer); 
      context.SaveChanges(); 

      // pull new customer 
      context = new Context(@"Data Source=.\SQLEXPRESS;Initial Catalog=EFTesting;Integrated Security=True;MultipleActiveResultSets=True"); 
      var result = context.Set<Customer>().Include(c => c.Schedule).Single(c => c.ID == customer.ID); 

      // this succeeds 
      Debug.Assert(result.ScheduleID == customer.Schedule.ID); 

      // this fails - Schedule is not set to database version, is left as new version from constructor 
      Debug.Assert(result.Schedule.ID == customer.Schedule.ID); 
     } 
    } 
} 

你可以看到默認的Schedule實例是在Customer構造函數中創建的,所以它永遠不會是空引用。但是,問題在於,當EF從數據庫加載客戶時,如果它不爲空,則不必費心設置計劃參考。

這會導致外鍵屬性ScheduleID與導航屬性不同步,並在稍後導致異常。

任何人都可以解釋爲什麼EF做到這一點,如果有辦法解決它而不改變模型設計?這對我來說就像是一個bug,即使這是在設計上,由於模型沒有被框架保持同步的事實。

+0

實例化默認構造函數中的導航(引用)屬性具有更難看的副作用:http://stackoverflow.com/questions/6779226/what-would-cause-the-entity-framework-to-save-an-unloaded - 丁懶加載-REF/6780912#6780912。只是不要這樣做。 – Slauma

回答

1

我對你的問題沒有一個很好的答案,但無論如何我會給它一個鏡頭。

基本上,您無法在構造函數中初始化Schedule屬性。通過這樣做,EF認爲該屬性已被修改(設置爲新值),並且不會嘗試覆蓋它。實際上,如果在代碼中的斷言之前添加另一個context.SaveChanges(),則會看到EF試圖插入您在構造函數中創建的新的Schedule實體。

唯一的解決方法我可以建議是手動初始化屬性的類或者,也許更好之外,創建其他客戶構造,使默認的一個受保護的或私有:

public class Customer 
{ 
    public Customer(string name) 
    { 
     Name = name; 
     Schedule = new Schedule(); 
    } 

    protected Customer() { } 

    public int ID { get; set; } 
    public string Name { get; set; } 
    public int ScheduleID { get; set; } 
    public Schedule Schedule { get; set; } 
    public byte[] Version { get; set; } 
} 

EF將使用默認構造函數,但可以在代碼中使用另一個。

我明白你的意思是如何看起來像一個錯誤,但我也理解爲什麼EF做它做的事情......我想我是在圍繞它。

無論如何,祝你好運!

+0

謝謝,我們決定使用工廠來實例化模型圖而不是替代構造函數,現在工作正常。 – Sam