下面是一個示例場景,說明我遇到的問題。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,即使這是在設計上,由於模型沒有被框架保持同步的事實。
實例化默認構造函數中的導航(引用)屬性具有更難看的副作用:http://stackoverflow.com/questions/6779226/what-would-cause-the-entity-framework-to-save-an-unloaded - 丁懶加載-REF/6780912#6780912。只是不要這樣做。 – Slauma