2013-08-08 2 views
3

У меня есть объект, обладающий свойством абстрактного типа. Это создает взаимосвязь «один к одному», которая использует наследование таблицы на иерархию. Кажется, все работает правильно.Не обновлять дискриминатор при изменении типа свойства

Я могу создать объект и установить Base property на ConcreteOne; все сохраняет правильно. Однако, когда я пытаюсь обновить Base до ConcreteTwo, EF обновляет запись Base в базе данных с новым пользовательским значением, она не обновляет дискриминатор для типа. Таким образом, дополнительные данные для ConcreteTwo сохраняются, но дискриминатор все еще говорит ConcreteOne.

Ниже приведен простой пример, который обнажает проблему

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      App_Start.EntityFrameworkProfilerBootstrapper.PreStart(); 

      Database.SetInitializer(new DropCreateDatabaseAlways<DataContext>()); 

      // Create our item with ConcreteOne for Base 
      using (var context = new DataContext()) 
      { 
       var item = new Item 
        { 
         Base = new ConcreteOne { Name = "Item", Data = 3 } 
        }; 
       context.Items.Add(item); 
       context.SaveChanges(); 
      } 

      // Update Base with a new ConcreteTwo 
      using (var context = new DataContext()) 
      { 
       var item = context.Items.FirstOrDefault(); 

       var newBase = new ConcreteTwo() 
        { 
         Item = item, 
         Name = "Item 3", 
         User = new User { Name = "Foo" } 
        }; 

       // If I don't set this to null, EF tries to create a new record in the DB which causes a PK exception 
       item.Base.Item = null; 
       item.Base = newBase; 

       // EF doesn't save the discriminator, but DOES save the User reference 
       context.SaveChanges(); 
      } 

      // Retrieve the item -- EF thinks Base is still ConcreteOne 
      using (var context = new DataContext()) 
      { 
       var item = context.Items.FirstOrDefault(); 
       Console.WriteLine("{0}: {1}", item.Name, item.Base.Name); 
      } 

      Console.WriteLine("Done."); 
      Console.ReadLine(); 
     } 
    } 

    public class DataContext : DbContext 
    { 
     public DbSet<Item> Items { get; set; } 
    } 

    public class User 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
    } 

    public class Item 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
     public virtual Base Base { get; set; } 
    } 

    public abstract class Base 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 

     [Required] 
     public virtual Item Item { get; set; } 
    } 

    public class ConcreteOne : Base 
    { 
     public int Data { get; set; } 
    } 

    public class ConcreteTwo : Base 
    { 
     public virtual User User { get; set; } 
    } 
} 

Когда изменения будут сохранены, EF генерирует следующий SQL:

update [dbo].[Bases] 
set [Name] = 'Item 3' /* @0 */, 
     [User_Id] = 1 /* @1 */ 
where (([Id] = 1 /* @2 */) 
     and [User_Id] is null) 

Так что это почти правильно, но я бы ожидать чтобы увидеть [Discriminator] = 'ConcreteTwo' в заявлении об обновлении. Неужели мои ожидания необоснованны или я делаю что-то неправильно?

В качестве теста я попытался использовать таблицу за один тип, и запись была удалена из таблицы ConcreteOne и добавлена ​​в таблицу ConcreteTwo, как и следовало ожидать. Так что это работает, но у моего реального приложения есть как минимум семь подтипов, а оператор SQL для извлечения свойства Base получил действительно противный. Поэтому я, конечно, хотел бы сделать это, используя TPH, если это возможно.

Обновление: Я проверял, что проблема существует в EF5, а также в EF6.

+1

Если EF поддерживает обновление с 1 типа к другому. Это baddddddd! Отвратительный противный EF. Похоже, что он работает по назначению, НЕ обновляя его. – Phill

+0

Почему бы не поддержать меняющиеся типы? В качестве тривиального примера, если у человека было свойство PrimaryTransportation типа Vehicle, разве не имело бы смысла позволять им изменять это с автомобиля на лодку? – Andorbal

+0

Автомобиль не может измениться на лодку. Если основной вид транспорта людей изменился, он не волшебным образом превратил свой автомобиль в лодку. Он покупает второй вид транспорта. – Phill

ответ

0

Этот вопрос основан на ожидании обновления, которое, по-видимому, является спорным ожиданием. В настоящее время лучше всего, если иерархия TPH не работает должным образом, и, учитывая, что EF6 в настоящее время находится в стадии бета-тестирования, необходимо начать обсуждение на Codeplex forums.

+0

Я буду смотреть на форумы, чтобы узнать, есть ли у кого-либо еще эта проблема, но я проверял, что проблема возникает и в EF5. Кроме того, я действительно просто ищу свой новый объект для сохранения, а старый - для удаления. Я начал путь обновления, потому что EF генерирует оператор UPDATE, когда он сохраняет мои изменения в базовой таблице. – Andorbal

+0

Что-то еще произошло со мной. Работает ли следующий код? (Я не могу запустить это сам прямо сейчас) – SteveChapman

+0

Извините - см. Следующий ответ, проблемы с форматированием ... – SteveChapman

-1

Я бы ожидал, что это создаст новый экземпляр (запись) с дискриминатором ConcreteTwo.

using (var context = new DataContext()) 
{ 
    var item = context.Items.FirstOrDefault(); 
    var newBase = new ConcreteTwo() 
    { 
     Name = "Item 3", 
     User = new User { Name = "Foo" } 
    }; 

    item.Base = newBase; 

    context.SaveChanges(); 
} 
+0

Я ожидал бы, что это сработает; однако это не так. Этот код вызовет исключение PK, потому что он пытается вставить новую запись ConcreteTwo, не удаляя сначала оригинальную запись ConcreteOne. При отношениях «один к одному» ПК зависимой записи должна быть равна основной записи. Если я добавлю в строку 'item.Base.Item = null' перед установкой' item.Base = newBase', EF помечает оригинал как удаленный, а новый добавляется. Но тогда это переводит это в инструкцию UPDATE ... – Andorbal

+0

Извинения, я пропустил один на один, хотя было сказано ... мое плохое; Я читал «Базу» как родителя во многих отношениях. Я бы сказал, что тогда это ожидалось. Если вы меняете свойство Base на новый экземпляр Concrete2, какой экземпляр элемента будет иметь исходный экземпляр Concrete1? Я бы сказал, что это была модельная проблема, а не проблема EF. – SteveChapman

+0

Я ожидаю, что исходный экземпляр ConcreteOne уйдет; поэтому isntance ConcreteOne больше не будет ссылаться на ** любой ** Item. Чтобы быть ясным, я намерен выбросить экземпляр ConcreteOne и полностью заменить его экземпляром ConcreteTwo. – Andorbal

0

Добавьте к вашей модели:

public enum BaseType 
{ 
    ConcreteOne = 1, 
    ConcreteTwo = 2 
} 

public abstract class Base 
{ 
    ... 
    public BaseType BaseType { get; set; } 
    ... 
} 

И в методе OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    modelBuilder.Entity<Base>() 
       .ToTable("Base"); 

    modelBuilder.Entity<ConcreteOne>() 
       .Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteOne)) 
       .ToTable("ConcreteOne"); 

    modelBuilder.Entity<ConcreteTwo>() 
       .Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteTwo)) 
       .ToTable("ConcreteTwo"); 
} 
+0

Это приведет к тому, что EF будет использовать таблицу за один тип, чего я бы хотел избежать. Я уже подтвердил, что TPT ** работает нормально, но я пытаюсь выполнить это с помощью TPH. – Andorbal