Код EF 4.1: почему EF не устанавливает это свойство навигации? - PullRequest
3 голосов
/ 15 августа 2011

Вот пример сценария, который иллюстрирует мою проблему.

Вот сценарий БД для создания базы данных в 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 делает это и есть ли способ работатьвокруг, не меняя дизайн модели?Мне кажется, что это ошибка, даже если это сделано из-за того, что модель не синхронизируется с фреймворком.

1 Ответ

1 голос
/ 16 августа 2011

У меня нет хорошего ответа на ваш вопрос, но я все равно попробую.

По сути, вы не можете инициализировать свойство Schedule в конструкторе.Делая это, EF думает, что свойство было изменено (установлено новое значение), и не будет пытаться перезаписать его.Фактически, если вы добавите еще один context.SaveChanges() перед утверждениями в своем коде, вы увидите, что EF пытается вставить новый объект Schedule, созданный вами в конструкторе.

Единственный способ, который я могу предложить, - этоинициализируйте свойство вручную вне класса или, возможно, лучше, создайте альтернативный конструктор Customer и сделайте его по умолчанию защищенным или закрытым:

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 делает то, что делает ... Думаю, я стою над этим.

В любом случае, удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...