У меня есть объект, обладающий свойством абстрактного типа. Это создает взаимосвязь «один к одному», которая использует наследование таблицы на иерархию. Кажется, все работает правильно.Не обновлять дискриминатор при изменении типа свойства
Я могу создать объект и установить 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.
Если EF поддерживает обновление с 1 типа к другому. Это baddddddd! Отвратительный противный EF. Похоже, что он работает по назначению, НЕ обновляя его. – Phill
Почему бы не поддержать меняющиеся типы? В качестве тривиального примера, если у человека было свойство PrimaryTransportation типа Vehicle, разве не имело бы смысла позволять им изменять это с автомобиля на лодку? – Andorbal
Автомобиль не может измениться на лодку. Если основной вид транспорта людей изменился, он не волшебным образом превратил свой автомобиль в лодку. Он покупает второй вид транспорта. – Phill